mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 11:55:53 +08:00
fix: update logic for handling data for uplot charts (#4070)
* fix: update logic for handling data for uplot charts * fix: handle NaN data
This commit is contained in:
parent
988ede7776
commit
1e4cf2513c
@ -14,7 +14,7 @@ export const optionsUpdateState = (
|
|||||||
if (lhsHeight !== rhsHeight || lhsWidth !== rhsWidth) {
|
if (lhsHeight !== rhsHeight || lhsWidth !== rhsWidth) {
|
||||||
state = 'update';
|
state = 'update';
|
||||||
}
|
}
|
||||||
if (Object.keys(lhs).length !== Object.keys(rhs).length) {
|
if (Object.keys(lhs)?.length !== Object.keys(rhs)?.length) {
|
||||||
return 'create';
|
return 'create';
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
@ -31,12 +31,12 @@ export const dataMatch = (
|
|||||||
lhs: uPlot.AlignedData,
|
lhs: uPlot.AlignedData,
|
||||||
rhs: uPlot.AlignedData,
|
rhs: uPlot.AlignedData,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (lhs.length !== rhs.length) {
|
if (lhs?.length !== rhs?.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return lhs.every((lhsOneSeries, seriesIdx) => {
|
return lhs.every((lhsOneSeries, seriesIdx) => {
|
||||||
const rhsOneSeries = rhs[seriesIdx];
|
const rhsOneSeries = rhs[seriesIdx];
|
||||||
if (lhsOneSeries.length !== rhsOneSeries.length) {
|
if (lhsOneSeries?.length !== rhsOneSeries?.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,52 @@ const themeColors = {
|
|||||||
hemlock: '#66664D',
|
hemlock: '#66664D',
|
||||||
vidaLoca: '#4D8000',
|
vidaLoca: '#4D8000',
|
||||||
rust: '#B33300',
|
rust: '#B33300',
|
||||||
|
red: '#FF0000', // Adding more colors, we need to get better colors from design team
|
||||||
|
blue: '#0000FF',
|
||||||
|
green: '#00FF00',
|
||||||
|
yellow: '#FFFF00',
|
||||||
|
purple: '#800080',
|
||||||
|
cyan: '#00FFFF',
|
||||||
|
magenta: '#FF00FF',
|
||||||
|
orange: '#FFA500',
|
||||||
|
pink: '#FFC0CB',
|
||||||
|
brown: '#A52A2A',
|
||||||
|
teal: '#008080',
|
||||||
|
lime: '#00FF00',
|
||||||
|
maroon: '#800000',
|
||||||
|
navy: '#000080',
|
||||||
|
aquamarine: '#7FFFD4',
|
||||||
|
gold: '#FFD700',
|
||||||
|
gray: '#808080',
|
||||||
|
skyBlue: '#87CEEB',
|
||||||
|
indigo: '#4B0082',
|
||||||
|
slateGray: '#708090',
|
||||||
|
chocolate: '#D2691E',
|
||||||
|
tomato: '#FF6347',
|
||||||
|
steelBlue: '#4682B4',
|
||||||
|
peru: '#CD853F',
|
||||||
|
darkOliveGreen: '#556B2F',
|
||||||
|
indianRed: '#CD5C5C',
|
||||||
|
mediumSlateBlue: '#7B68EE',
|
||||||
|
rosyBrown: '#BC8F8F',
|
||||||
|
darkSlateGray: '#2F4F4F',
|
||||||
|
mediumAquamarine: '#66CDAA',
|
||||||
|
lavender: '#E6E6FA',
|
||||||
|
thistle: '#D8BFD8',
|
||||||
|
salmon: '#FA8072',
|
||||||
|
darkSalmon: '#E9967A',
|
||||||
|
paleVioletRed: '#DB7093',
|
||||||
|
mediumPurple: '#9370DB',
|
||||||
|
darkOrchid: '#9932CC',
|
||||||
|
lawnGreen: '#7CFC00',
|
||||||
|
mediumSeaGreen: '#3CB371',
|
||||||
|
lightCoral: '#F08080',
|
||||||
|
darkSeaGreen: '#8FBC8F',
|
||||||
|
sandyBrown: '#F4A460',
|
||||||
|
darkKhaki: '#BDB76B',
|
||||||
|
cornflowerBlue: '#6495ED',
|
||||||
|
mediumVioletRed: '#C71585',
|
||||||
|
paleGreen: '#98FB98',
|
||||||
},
|
},
|
||||||
errorColor: '#d32f2f',
|
errorColor: '#d32f2f',
|
||||||
royalGrey: '#888888',
|
royalGrey: '#888888',
|
||||||
|
@ -28,7 +28,7 @@ function GridCardGraph({
|
|||||||
isQueryEnabled,
|
isQueryEnabled,
|
||||||
threshold,
|
threshold,
|
||||||
variables,
|
variables,
|
||||||
filterNaN,
|
fillSpans = false,
|
||||||
}: GridCardGraphProps): JSX.Element {
|
}: GridCardGraphProps): JSX.Element {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
@ -90,11 +90,7 @@ function GridCardGraph({
|
|||||||
|
|
||||||
const containerDimensions = useResizeObserver(graphRef);
|
const containerDimensions = useResizeObserver(graphRef);
|
||||||
|
|
||||||
const chartData = getUPlotChartData(
|
const chartData = getUPlotChartData(queryResponse?.data?.payload, fillSpans);
|
||||||
queryResponse?.data?.payload,
|
|
||||||
undefined,
|
|
||||||
filterNaN,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export interface GridCardGraphProps {
|
|||||||
headerMenuList?: WidgetGraphComponentProps['headerMenuList'];
|
headerMenuList?: WidgetGraphComponentProps['headerMenuList'];
|
||||||
isQueryEnabled: boolean;
|
isQueryEnabled: boolean;
|
||||||
variables?: Dashboard['data']['variables'];
|
variables?: Dashboard['data']['variables'];
|
||||||
filterNaN?: boolean;
|
fillSpans?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetGraphVisibilityStateOnLegendClickProps {
|
export interface GetGraphVisibilityStateOnLegendClickProps {
|
||||||
|
@ -107,7 +107,7 @@ function DBCall(): JSX.Element {
|
|||||||
<Card data-testid="database_call_rps">
|
<Card data-testid="database_call_rps">
|
||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
<Graph
|
<Graph
|
||||||
filterNaN
|
fillSpans={false}
|
||||||
name="database_call_rps"
|
name="database_call_rps"
|
||||||
widget={databaseCallsRPSWidget}
|
widget={databaseCallsRPSWidget}
|
||||||
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
|
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
|
||||||
@ -141,7 +141,7 @@ function DBCall(): JSX.Element {
|
|||||||
<Card data-testid="database_call_avg_duration">
|
<Card data-testid="database_call_avg_duration">
|
||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
<Graph
|
<Graph
|
||||||
filterNaN
|
fillSpans
|
||||||
name="database_call_avg_duration"
|
name="database_call_avg_duration"
|
||||||
widget={databaseCallsAverageDurationWidget}
|
widget={databaseCallsAverageDurationWidget}
|
||||||
headerMenuList={MENU_ITEMS}
|
headerMenuList={MENU_ITEMS}
|
||||||
|
@ -148,7 +148,7 @@ function External(): JSX.Element {
|
|||||||
<Card data-testid="external_call_error_percentage">
|
<Card data-testid="external_call_error_percentage">
|
||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
<Graph
|
<Graph
|
||||||
filterNaN
|
fillSpans={false}
|
||||||
headerMenuList={MENU_ITEMS}
|
headerMenuList={MENU_ITEMS}
|
||||||
name="external_call_error_percentage"
|
name="external_call_error_percentage"
|
||||||
widget={externalCallErrorWidget}
|
widget={externalCallErrorWidget}
|
||||||
@ -184,7 +184,7 @@ function External(): JSX.Element {
|
|||||||
<Card data-testid="external_call_duration">
|
<Card data-testid="external_call_duration">
|
||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
<Graph
|
<Graph
|
||||||
filterNaN
|
fillSpans
|
||||||
name="external_call_duration"
|
name="external_call_duration"
|
||||||
headerMenuList={MENU_ITEMS}
|
headerMenuList={MENU_ITEMS}
|
||||||
widget={externalCallDurationWidget}
|
widget={externalCallDurationWidget}
|
||||||
@ -221,7 +221,7 @@ function External(): JSX.Element {
|
|||||||
<Card data-testid="external_call_rps_by_address">
|
<Card data-testid="external_call_rps_by_address">
|
||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
<Graph
|
<Graph
|
||||||
filterNaN
|
fillSpans
|
||||||
name="external_call_rps_by_address"
|
name="external_call_rps_by_address"
|
||||||
widget={externalCallRPSWidget}
|
widget={externalCallRPSWidget}
|
||||||
headerMenuList={MENU_ITEMS}
|
headerMenuList={MENU_ITEMS}
|
||||||
@ -260,7 +260,7 @@ function External(): JSX.Element {
|
|||||||
name="external_call_duration_by_address"
|
name="external_call_duration_by_address"
|
||||||
widget={externalCallDurationAddressWidget}
|
widget={externalCallDurationAddressWidget}
|
||||||
headerMenuList={MENU_ITEMS}
|
headerMenuList={MENU_ITEMS}
|
||||||
filterNaN
|
fillSpans
|
||||||
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
|
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
|
||||||
onGraphClickHandler(setSelectedTimeStamp)(
|
onGraphClickHandler(setSelectedTimeStamp)(
|
||||||
xValue,
|
xValue,
|
||||||
|
@ -84,7 +84,7 @@ function ApDexMetrics({
|
|||||||
return (
|
return (
|
||||||
<Graph
|
<Graph
|
||||||
name="apdex"
|
name="apdex"
|
||||||
filterNaN
|
fillSpans={false}
|
||||||
widget={apDexMetricsWidget}
|
widget={apDexMetricsWidget}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
onClickHandler={handleGraphClick('ApDex')}
|
onClickHandler={handleGraphClick('ApDex')}
|
||||||
|
@ -88,7 +88,7 @@ function ServiceOverview({
|
|||||||
widget={latencyWidget}
|
widget={latencyWidget}
|
||||||
onClickHandler={handleGraphClick('Service')}
|
onClickHandler={handleGraphClick('Service')}
|
||||||
isQueryEnabled={isQueryEnabled}
|
isQueryEnabled={isQueryEnabled}
|
||||||
filterNaN
|
fillSpans={false}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -27,7 +27,7 @@ function TopLevelOperation({
|
|||||||
) : (
|
) : (
|
||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
<Graph
|
<Graph
|
||||||
filterNaN
|
fillSpans={false}
|
||||||
name={name}
|
name={name}
|
||||||
widget={widget}
|
widget={widget}
|
||||||
onClickHandler={handleGraphClick(opName)}
|
onClickHandler={handleGraphClick(opName)}
|
||||||
|
@ -1,20 +1,52 @@
|
|||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
|
||||||
function filterIsNan(
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
value: [number, string],
|
function fillMissingTimestamps(
|
||||||
isFilterNaNEnabled?: boolean,
|
sortedTimestamps: number[],
|
||||||
): boolean {
|
subsetArray: any[],
|
||||||
const val = value[1];
|
fillSpans: boolean | undefined,
|
||||||
return isFilterNaNEnabled ? val !== 'NaN' : true;
|
): any[] {
|
||||||
|
const filledArray = [];
|
||||||
|
|
||||||
|
let subsetIndex = 0;
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const timestamp of sortedTimestamps) {
|
||||||
|
if (
|
||||||
|
subsetIndex < subsetArray.length &&
|
||||||
|
timestamp === subsetArray[subsetIndex][0]
|
||||||
|
) {
|
||||||
|
// Timestamp is present in subsetArray
|
||||||
|
const seriesPointData = subsetArray[subsetIndex];
|
||||||
|
|
||||||
|
if (
|
||||||
|
seriesPointData &&
|
||||||
|
Array.isArray(seriesPointData) &&
|
||||||
|
seriesPointData.length > 0 &&
|
||||||
|
seriesPointData[1] !== 'NaN'
|
||||||
|
) {
|
||||||
|
filledArray.push(subsetArray[subsetIndex]);
|
||||||
|
} else {
|
||||||
|
const value = fillSpans ? 0 : null;
|
||||||
|
filledArray.push([seriesPointData[0], value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
subsetIndex += 1;
|
||||||
|
} else {
|
||||||
|
// Timestamp is missing in subsetArray, fill with [timestamp, 0]
|
||||||
|
const value = fillSpans ? 0 : null;
|
||||||
|
filledArray.push([timestamp, value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filledArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getUPlotChartData = (
|
export const getUPlotChartData = (
|
||||||
apiResponse?: MetricRangePayloadProps,
|
apiResponse?: MetricRangePayloadProps,
|
||||||
fillSpans?: boolean,
|
fillSpans?: boolean,
|
||||||
filterNaN?: boolean,
|
): any[] => {
|
||||||
): uPlot.AlignedData => {
|
|
||||||
const seriesList = apiResponse?.data?.result || [];
|
const seriesList = apiResponse?.data?.result || [];
|
||||||
const uPlotData: uPlot.AlignedData = [];
|
const uPlotData = [];
|
||||||
|
|
||||||
// this helps us identify the series with the max number of values and helps define the x axis - timestamps
|
// this helps us identify the series with the max number of values and helps define the x axis - timestamps
|
||||||
const xSeries = seriesList.reduce(
|
const xSeries = seriesList.reduce(
|
||||||
@ -28,33 +60,28 @@ export const getUPlotChartData = (
|
|||||||
seriesList[index]?.values?.sort((a, b) => a[0] - b[0]);
|
seriesList[index]?.values?.sort((a, b) => a[0] - b[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestampArr = xSeries?.values
|
const timestampArr = xSeries?.values?.map((v) => v[0]);
|
||||||
?.filter((response) => filterIsNan(response, filterNaN))
|
|
||||||
.map((v) => v[0]);
|
|
||||||
|
|
||||||
const uplotDataFormatArr = new Float64Array(timestampArr);
|
|
||||||
|
|
||||||
// timestamp
|
// timestamp
|
||||||
uPlotData.push(uplotDataFormatArr);
|
uPlotData.push(timestampArr);
|
||||||
|
|
||||||
const numberOfTimestamps = uPlotData[0].length;
|
|
||||||
|
|
||||||
// for each series, push the values
|
// for each series, push the values
|
||||||
seriesList.forEach((series) => {
|
seriesList.forEach((series) => {
|
||||||
|
const updatedSeries = fillMissingTimestamps(
|
||||||
|
timestampArr,
|
||||||
|
series?.values || [],
|
||||||
|
fillSpans,
|
||||||
|
);
|
||||||
|
|
||||||
const seriesData =
|
const seriesData =
|
||||||
series?.values
|
updatedSeries?.map((v) => {
|
||||||
?.filter((response) => filterIsNan(response, filterNaN))
|
if (v[1] === null) {
|
||||||
.map((v) => parseFloat(v[1])) || [];
|
return v[1];
|
||||||
|
|
||||||
// fill rest of the value with zero
|
|
||||||
if (seriesData.length < numberOfTimestamps && fillSpans) {
|
|
||||||
const diff = numberOfTimestamps - seriesData.length;
|
|
||||||
for (let i = 0; i < diff; i += 1) {
|
|
||||||
seriesData.push(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return parseFloat(v[1]);
|
||||||
|
}) || [];
|
||||||
|
|
||||||
uPlotData.push(new Float64Array(seriesData));
|
uPlotData.push(seriesData);
|
||||||
});
|
});
|
||||||
|
|
||||||
return uPlotData;
|
return uPlotData;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user