import { themeColors } from 'constants/theme'; import getLabelName from 'lib/getLabelName'; import { isUndefined } from 'lodash-es'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { QueryData } from 'types/api/widgets/getQuery'; import { normalizePlotValue } from './dataUtils'; import { generateColor } from './generateColor'; function getXAxisTimestamps(seriesList: QueryData[]): number[] { const timestamps = new Set(); seriesList.forEach((series: { values?: [number, string][] }) => { if (series?.values) { series.values.forEach((value) => { timestamps.add(value[0]); }); } }); const timestampsArr = Array.from(timestamps); timestampsArr.sort((a, b) => a - b); return timestampsArr; } // eslint-disable-next-line sonarjs/cognitive-complexity function fillMissingXAxisTimestamps( timestampArr: number[], data: Array<{ values?: [number, string][] }>, ): (number | null)[][] { // Generate a set of all timestamps in the range const allTimestampsSet = new Set(timestampArr); const result: (number | null)[][] = []; // Process each series entry for (let i = 0; i < data.length; i++) { const entry = data[i]; if (!entry?.values) { result.push([]); } else { // Build Set of existing timestamps directly (avoid intermediate array) const existingTimestamps = new Set(); const valuesMap = new Map(); for (let j = 0; j < entry.values.length; j++) { const [timestamp, value] = entry.values[j]; existingTimestamps.add(timestamp); valuesMap.set(timestamp, normalizePlotValue(value)); } // Find missing timestamps by iterating Set directly (avoid Array.from + filter) const missingTimestamps: number[] = []; const allTimestampsArray = Array.from(allTimestampsSet); for (let k = 0; k < allTimestampsArray.length; k++) { const timestamp = allTimestampsArray[k]; if (!existingTimestamps.has(timestamp)) { missingTimestamps.push(timestamp); } } // Add missing timestamps to map for (let j = 0; j < missingTimestamps.length; j++) { valuesMap.set(missingTimestamps[j], null); } // Build sorted array of values const sortedTimestamps = Array.from(valuesMap.keys()).sort((a, b) => a - b); const yValues = sortedTimestamps.map((timestamp) => { const value = valuesMap.get(timestamp); return value !== undefined ? value : null; }); result.push(yValues); } } return result; } function getStackedSeries(val: (number | null)[][]): (number | null)[][] { const series = val ? val.map((row: (number | null)[]) => [...row]) : []; for (let i = series.length - 2; i >= 0; i--) { for (let j = 0; j < series[i].length; j++) { series[i][j] = (series[i][j] || 0) + (series[i + 1][j] || 0); } } return series; } export const getUPlotChartData = ( apiResponse?: MetricRangePayloadProps, fillSpans?: boolean, stackedBarChart?: boolean, hiddenGraph?: { [key: string]: boolean; }, ): any[] => { const seriesList = apiResponse?.data?.result || []; const timestampArr = getXAxisTimestamps(seriesList); const yAxisValuesArr = fillMissingXAxisTimestamps(timestampArr, seriesList); return [ timestampArr, ...(stackedBarChart && isUndefined(hiddenGraph) ? getStackedSeries(yAxisValuesArr) : yAxisValuesArr), ]; }; const processAnomalyDetectionData = ( anomalyDetectionData: any, isDarkMode: boolean, // eslint-disable-next-line sonarjs/cognitive-complexity ): Record => { if (!anomalyDetectionData) { return {}; } const processedData: Record< string, { data: number[][]; color: string; legendLabel: string } > = {}; for ( let queryIndex = 0; queryIndex < anomalyDetectionData.length; queryIndex++ ) { const queryData = anomalyDetectionData[queryIndex]; const { series, predictedSeries, upperBoundSeries, lowerBoundSeries, queryName, legend, } = queryData; for (let index = 0; index < series?.length; index++) { const label = getLabelName( series[index].labels, queryName || '', // query legend || '', ); const objKey = anomalyDetectionData.length > 1 ? `${queryName}-${label}` : label; // Single iteration instead of 5 separate map operations const { values: seriesValues } = series[index] || { values: [] }; const { values: predictedValues } = predictedSeries[index] || { values: [] }; const { values: upperBoundValues } = upperBoundSeries[index] || { values: [], }; const { values: lowerBoundValues } = lowerBoundSeries[index] || { values: [], }; const length = seriesValues.length; const timestamps: number[] = new Array(length); const values: number[] = new Array(length); const predicted: number[] = new Array(length); const upperBound: number[] = new Array(length); const lowerBound: number[] = new Array(length); for (let i = 0; i < length; i++) { timestamps[i] = seriesValues[i].timestamp / 1000; values[i] = seriesValues[i]?.value ?? null; predicted[i] = predictedValues[i]?.value ?? null; upperBound[i] = upperBoundValues[i]?.value ?? null; lowerBound[i] = lowerBoundValues[i]?.value ?? null; } processedData[objKey] = { data: [timestamps, values, predicted, upperBound, lowerBound], color: generateColor( objKey, isDarkMode ? themeColors.chartcolors : themeColors.lightModeColor, ), legendLabel: label, }; } } return processedData; }; export const getUplotChartDataForAnomalyDetection = ( apiResponse: MetricRangePayloadProps, isDarkMode: boolean, ): Record => { const anomalyDetectionData = apiResponse?.data?.newResult?.data?.result; return processAnomalyDetectionData(anomalyDetectionData, isDarkMode); };