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:
Yunus M 2023-11-27 16:57:41 +05:30 committed by GitHub
parent 988ede7776
commit 1e4cf2513c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 117 additions and 48 deletions

View File

@ -14,7 +14,7 @@ export const optionsUpdateState = (
if (lhsHeight !== rhsHeight || lhsWidth !== rhsWidth) {
state = 'update';
}
if (Object.keys(lhs).length !== Object.keys(rhs).length) {
if (Object.keys(lhs)?.length !== Object.keys(rhs)?.length) {
return 'create';
}
// eslint-disable-next-line no-restricted-syntax
@ -31,12 +31,12 @@ export const dataMatch = (
lhs: uPlot.AlignedData,
rhs: uPlot.AlignedData,
): boolean => {
if (lhs.length !== rhs.length) {
if (lhs?.length !== rhs?.length) {
return false;
}
return lhs.every((lhsOneSeries, seriesIdx) => {
const rhsOneSeries = rhs[seriesIdx];
if (lhsOneSeries.length !== rhsOneSeries.length) {
if (lhsOneSeries?.length !== rhsOneSeries?.length) {
return false;
}

View File

@ -30,6 +30,52 @@ const themeColors = {
hemlock: '#66664D',
vidaLoca: '#4D8000',
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',
royalGrey: '#888888',

View File

@ -28,7 +28,7 @@ function GridCardGraph({
isQueryEnabled,
threshold,
variables,
filterNaN,
fillSpans = false,
}: GridCardGraphProps): JSX.Element {
const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>();
@ -90,11 +90,7 @@ function GridCardGraph({
const containerDimensions = useResizeObserver(graphRef);
const chartData = getUPlotChartData(
queryResponse?.data?.payload,
undefined,
filterNaN,
);
const chartData = getUPlotChartData(queryResponse?.data?.payload, fillSpans);
const isDarkMode = useIsDarkMode();

View File

@ -39,7 +39,7 @@ export interface GridCardGraphProps {
headerMenuList?: WidgetGraphComponentProps['headerMenuList'];
isQueryEnabled: boolean;
variables?: Dashboard['data']['variables'];
filterNaN?: boolean;
fillSpans?: boolean;
}
export interface GetGraphVisibilityStateOnLegendClickProps {

View File

@ -107,7 +107,7 @@ function DBCall(): JSX.Element {
<Card data-testid="database_call_rps">
<GraphContainer>
<Graph
filterNaN
fillSpans={false}
name="database_call_rps"
widget={databaseCallsRPSWidget}
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
@ -141,7 +141,7 @@ function DBCall(): JSX.Element {
<Card data-testid="database_call_avg_duration">
<GraphContainer>
<Graph
filterNaN
fillSpans
name="database_call_avg_duration"
widget={databaseCallsAverageDurationWidget}
headerMenuList={MENU_ITEMS}

View File

@ -148,7 +148,7 @@ function External(): JSX.Element {
<Card data-testid="external_call_error_percentage">
<GraphContainer>
<Graph
filterNaN
fillSpans={false}
headerMenuList={MENU_ITEMS}
name="external_call_error_percentage"
widget={externalCallErrorWidget}
@ -184,7 +184,7 @@ function External(): JSX.Element {
<Card data-testid="external_call_duration">
<GraphContainer>
<Graph
filterNaN
fillSpans
name="external_call_duration"
headerMenuList={MENU_ITEMS}
widget={externalCallDurationWidget}
@ -221,7 +221,7 @@ function External(): JSX.Element {
<Card data-testid="external_call_rps_by_address">
<GraphContainer>
<Graph
filterNaN
fillSpans
name="external_call_rps_by_address"
widget={externalCallRPSWidget}
headerMenuList={MENU_ITEMS}
@ -260,7 +260,7 @@ function External(): JSX.Element {
name="external_call_duration_by_address"
widget={externalCallDurationAddressWidget}
headerMenuList={MENU_ITEMS}
filterNaN
fillSpans
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
onGraphClickHandler(setSelectedTimeStamp)(
xValue,

View File

@ -84,7 +84,7 @@ function ApDexMetrics({
return (
<Graph
name="apdex"
filterNaN
fillSpans={false}
widget={apDexMetricsWidget}
onDragSelect={onDragSelect}
onClickHandler={handleGraphClick('ApDex')}

View File

@ -88,7 +88,7 @@ function ServiceOverview({
widget={latencyWidget}
onClickHandler={handleGraphClick('Service')}
isQueryEnabled={isQueryEnabled}
filterNaN
fillSpans={false}
/>
</GraphContainer>
</Card>

View File

@ -27,7 +27,7 @@ function TopLevelOperation({
) : (
<GraphContainer>
<Graph
filterNaN
fillSpans={false}
name={name}
widget={widget}
onClickHandler={handleGraphClick(opName)}

View File

@ -1,20 +1,52 @@
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
function filterIsNan(
value: [number, string],
isFilterNaNEnabled?: boolean,
): boolean {
const val = value[1];
return isFilterNaNEnabled ? val !== 'NaN' : true;
// eslint-disable-next-line sonarjs/cognitive-complexity
function fillMissingTimestamps(
sortedTimestamps: number[],
subsetArray: any[],
fillSpans: boolean | undefined,
): 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 = (
apiResponse?: MetricRangePayloadProps,
fillSpans?: boolean,
filterNaN?: boolean,
): uPlot.AlignedData => {
): any[] => {
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
const xSeries = seriesList.reduce(
@ -28,33 +60,28 @@ export const getUPlotChartData = (
seriesList[index]?.values?.sort((a, b) => a[0] - b[0]);
}
const timestampArr = xSeries?.values
?.filter((response) => filterIsNan(response, filterNaN))
.map((v) => v[0]);
const uplotDataFormatArr = new Float64Array(timestampArr);
const timestampArr = xSeries?.values?.map((v) => v[0]);
// timestamp
uPlotData.push(uplotDataFormatArr);
const numberOfTimestamps = uPlotData[0].length;
uPlotData.push(timestampArr);
// for each series, push the values
seriesList.forEach((series) => {
const updatedSeries = fillMissingTimestamps(
timestampArr,
series?.values || [],
fillSpans,
);
const seriesData =
series?.values
?.filter((response) => filterIsNan(response, filterNaN))
.map((v) => parseFloat(v[1])) || [];
updatedSeries?.map((v) => {
if (v[1] === null) {
return v[1];
}
return parseFloat(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);
}
}
uPlotData.push(new Float64Array(seriesData));
uPlotData.push(seriesData);
});
return uPlotData;