// Copyright 2026 OpenObserve Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { describe, expect, it, vi, beforeEach } from "vitest"; import { convertMultiSQLData, convertSQLData } from "@/utils/dashboard/convertSQLData"; import { calculateGridPositions } from "@/utils/dashboard/calculateGridForSubPlot"; // Mock external dependencies vi.mock("date-fns-tz", () => ({ toZonedTime: vi.fn((date) => new Date(date)), })); vi.mock("@/utils/dashboard/datetimeStartPoint", () => ({ dateBin: vi.fn(), })); vi.mock("date-fns", () => ({ format: vi.fn((date, formatStr) => { if (formatStr === "yyyy-MM-dd HH:mm:ss") return "2024-01-01 12:00:00"; return "2024-01-01"; }), })); vi.mock("@/utils/dashboard/convertDataIntoUnitValue", () => ({ formatUnitValue: vi.fn(() => ({ value: "100", unit: "" })), getUnitValue: vi.fn(() => ({ value: "100", unit: "" })), calculateBottomLegendHeight: vi.fn( (legendCount, chartWidth, series, maxHeight, legendConfig, gridConfig, chartHeight) => { if (legendConfig && gridConfig && chartHeight) { legendConfig.top = chartHeight - 90; legendConfig.height = 70; gridConfig.bottom = 90; } return 90; }, ), calculateRightLegendWidth: vi.fn(() => 160), calculateChartDimensions: vi.fn(() => ({ availableWidth: 800, availableHeight: 400, legendSpace: { right: 160, bottom: 90 }, gridSpace: { left: 60, right: 160, top: 60, bottom: 90 }, })), calculatePieChartRadius: vi.fn(() => 150), })); vi.mock("@/utils/dashboard/chartDimensionUtils", () => ({ calculateOptimalFontSize: vi.fn(() => 12), calculateWidthText: vi.fn(() => 100), calculateDynamicNameGap: vi.fn(() => 25), calculateRotatedLabelBottomSpace: vi.fn(() => 0), X_AXIS_TICK_LABEL_BAND: 20, })); vi.mock("@/utils/dashboard/chartColorUtils", () => ({ getContrastColor: vi.fn(() => "#000000"), applySeriesColorMappings: vi.fn((series) => series), })); vi.mock("@/utils/dashboard/dateTimeUtils", () => ({ formatDate: vi.fn(() => "2024-01-01"), isTimeSeries: vi.fn(() => false), isTimeStamp: vi.fn(() => false), })); vi.mock("@/utils/dashboard/calculateGridForSubPlot", () => ({ calculateGridPositions: vi.fn(() => ({ gridArray: [{ left: "0%", top: "0%", width: "100%", height: "100%" }], gridWidth: 800, gridHeight: 400, gridNoOfRow: 1, gridNoOfCol: 1, })), getTrellisGrid: vi.fn(() => ({ gridArray: [] })), })); vi.mock("../query/sqlUtils", () => ({ isGivenFieldInOrderBy: vi.fn(() => false), })); vi.mock("./colorPalette", () => ({ ColorModeWithoutMinMax: vi.fn(), getSeriesColor: vi.fn(() => "#ff0000"), getSQLMinMaxValue: vi.fn(() => [0, 100]), getColorPalette: vi.fn(() => ["#ff0000", "#00ff00", "#0000ff"]), getSeriesHash: vi.fn(() => 0), getAreaGradientColor: vi.fn(() => ({ type: "linear", colorStops: [] })), getGridLineStyle: vi.fn(() => ({ type: "dashed", width: 1, color: "rgba(0,0,0,0.08)" })), getAreaStyleOverride: vi.fn((_type, base) => base ? { areaStyle: { ...base, color: { type: "linear", colorStops: [] } } } : {}, ), })); vi.mock("@/utils/zincutils", () => ({ deepCopy: vi.fn((obj) => JSON.parse(JSON.stringify(obj))), })); vi.mock("@/utils/dashboard/getAnnotationsData", () => ({ getAnnotationsData: vi.fn(() => []), })); describe("convertSQLData", () => { let mockPanelSchema: any; let mockStore: any; let mockChartPanelRef: any; let mockHoveredSeriesState: any; let mockResultMetaData: any; let mockMetadata: any; let mockChartPanelStyle: any; let mockAnnotations: any; beforeEach(() => { vi.clearAllMocks(); mockPanelSchema = { queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], z: [], breakdown: [], }, }, ], config: { top_results: 10, top_results_others: false, }, type: "line", }; mockStore = { state: { theme: "light", zoConfig: { max_dashboard_series: 100, }, }, }; mockChartPanelRef = { value: { offsetWidth: 800, offsetHeight: 400, }, }; mockHoveredSeriesState = {}; mockResultMetaData = [{}]; mockMetadata = { queries: [{}] }; mockChartPanelStyle = {}; mockAnnotations = []; }); describe("convertSQLData function", () => { it("should return null options when searchQueryData is empty array", async () => { const result = await convertSQLData( mockPanelSchema, [], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toEqual({ options: null }); }); it("should return null options when searchQueryData is not an array", async () => { const result = await convertSQLData( mockPanelSchema, null, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toEqual({ options: null }); }); it("should return null options when searchQueryData[0] is null", async () => { const result = await convertSQLData( mockPanelSchema, [null], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toEqual({ options: null }); }); it("should return null options when panelSchema.queries[0].fields.x is missing", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: null, y: [{ alias: "value" }], z: [], breakdown: [], }, }, ], }; const result = await convertSQLData( schema, [[]], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toEqual({ options: null }); }); it("should return null options when panelSchema.queries[0].fields.y is missing", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: null, z: [], breakdown: [], }, }, ], }; const result = await convertSQLData( schema, [[]], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toEqual({ options: null }); }); }); describe("convertMultiSQLData function", () => { it("should handle empty searchQueryData", async () => { const result = await convertMultiSQLData( mockPanelSchema, [], mockStore, mockChartPanelRef, mockHoveredSeriesState, { value: [[{}]] }, { queries: [{}] }, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); it("should handle null searchQueryData", async () => { const result = await convertMultiSQLData( mockPanelSchema, null, mockStore, mockChartPanelRef, mockHoveredSeriesState, { value: [[{}]] }, { queries: [{}] }, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); it("should handle non-array searchQueryData", async () => { const result = await convertMultiSQLData( mockPanelSchema, "not-an-array", mockStore, mockChartPanelRef, mockHoveredSeriesState, { value: [[{}]] }, { queries: [{}] }, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); it("should process single query data correctly", async () => { const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const resultMetaData = { value: [[{}]] }; const metadata = { queries: [{}] }; const result = await convertMultiSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); expect(Array.isArray(result.options.series)).toBe(true); }); it("should process multiple queries data correctly", async () => { const searchData = [ [{ timestamp: "2023-01-01", value: 10 }], [{ timestamp: "2023-01-01", value: 20 }], ]; const resultMetaData = { value: [[{}], [{}]] }; const metadata = { queries: [{}, {}] }; const result = await convertMultiSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); expect(Array.isArray(result.options.series)).toBe(true); expect(result.options.series.length).toBeGreaterThanOrEqual(1); }); it("should pass loading parameter through to convertSQLData", async () => { const searchData = [[{ timestamp: "2023-01-01", value: 10 }]]; const result = await convertMultiSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, { value: [[{}]] }, { queries: [{}] }, mockChartPanelStyle, mockAnnotations, true, // loading = streaming ); // Should not throw and should return valid options expect(result.options).toBeDefined(); }); it("should default loading to undefined when not provided", async () => { const searchData = [[{ timestamp: "2023-01-01", value: 10 }]]; const result = await convertMultiSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, { value: [[{}]] }, { queries: [{}] }, mockChartPanelStyle, mockAnnotations, // loading omitted — should default to undefined (final render) ); expect(result.options).toBeDefined(); }); }); describe("loading parameter in convertSQLData", () => { it("should accept loading=true without error", async () => { const result = await convertSQLData( mockPanelSchema, [[{ timestamp: "2023-01-01", value: 10 }]], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, true, // loading ); expect(result.options).toBeDefined(); }); it("should accept loading=false without error", async () => { const result = await convertSQLData( mockPanelSchema, [[{ timestamp: "2023-01-01", value: 10 }]], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, false, // loading ); expect(result.options).toBeDefined(); }); }); describe("Internal utility functions", () => { // Need to access internal functions through dynamic import or reflection // These functions are not exported, so we'll test them through the public API behavior describe("processData function behavior", () => { it("should handle empty data array", async () => { const result = await convertSQLData( mockPanelSchema, [[]], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(Array.isArray(result.options.series)).toBe(true); }); it("should handle data without breakdown keys", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle top_results configuration", async () => { const schema = { ...mockPanelSchema, config: { top_results: 5, top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, { timestamp: "2023-01-03", value: 30, category: "C" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle missing breakdown values", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: null }, { timestamp: "2023-01-03", value: 30, category: "" }, { timestamp: "2023-01-04", value: 40, category: undefined }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle processData with top_results enabled", async () => { const schema = { ...mockPanelSchema, config: { top_results: 2, top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100, category: "A" }, { timestamp: "2023-01-02", value: 50, category: "B" }, { timestamp: "2023-01-03", value: 25, category: "C" }, { timestamp: "2023-01-04", value: 10, category: "D" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle top_results_others configuration", async () => { const schema = { ...mockPanelSchema, config: { top_results: 2, top_results_others: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100, category: "A" }, { timestamp: "2023-01-02", value: 80, category: "B" }, { timestamp: "2023-01-03", value: 40, category: "C" }, { timestamp: "2023-01-04", value: 20, category: "D" }, { timestamp: "2023-01-05", value: 10, category: "E" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle store max_dashboard_series limit", async () => { const storeWithLimit = { state: { zoConfig: { max_dashboard_series: 3, }, }, }; const schema = { ...mockPanelSchema, config: { top_results: 5, top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100, category: "A" }, { timestamp: "2023-01-02", value: 90, category: "B" }, { timestamp: "2023-01-03", value: 80, category: "C" }, { timestamp: "2023-01-04", value: 70, category: "D" }, { timestamp: "2023-01-05", value: 60, category: "E" }, ], ]; const result = await convertSQLData( schema, searchData, storeWithLimit, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); }); describe("Chart type handling", () => { it("should handle different chart types", async () => { const chartTypes = ["line", "bar", "area", "scatter"]; for (const chartType of chartTypes) { const schema = { ...mockPanelSchema, type: chartType, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); } }); it("should handle pie/donut charts", async () => { const schema = { ...mockPanelSchema, type: "pie", queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { category: "A", value: 10 }, { category: "B", value: 20 }, { category: "C", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle heatmap charts", async () => { const schema = { ...mockPanelSchema, type: "heatmap", queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "category" }], z: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", category: "A", value: 10 }, { timestamp: "2023-01-02", category: "B", value: 20 }, { timestamp: "2023-01-03", category: "C", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle stacked charts", async () => { const schema = { ...mockPanelSchema, type: "stacked", queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, { timestamp: "2023-01-03", value: 30, category: "A" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle area-stacked charts", async () => { const schema = { ...mockPanelSchema, type: "area-stacked", queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle donut charts", async () => { const schema = { ...mockPanelSchema, type: "donut", queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { category: "A", value: 10 }, { category: "B", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); }); describe("Error handling and edge cases", () => { it("should handle malformed data gracefully", async () => { const searchData = [ [ { timestamp: null, value: 10 }, { timestamp: "invalid-date", value: "not-a-number" }, { timestamp: "", value: null }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle very large datasets", async () => { const largeData = Array.from({ length: 1000 }, (_, i) => ({ timestamp: `2023-01-${String(i + 1).padStart(2, "0")}`, value: Math.random() * 100, })); const searchData = [largeData]; const result = await convertSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle missing store configuration", async () => { const storeWithoutConfig = { state: { zoConfig: null, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, storeWithoutConfig, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); }); describe("Mark line functionality", () => { it("should handle mark lines with xAxis type", async () => { const schema = { ...mockPanelSchema, config: { mark_line: [ { name: "Threshold", type: "xAxis", value: "2023-01-01", }, ], }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle mark lines with yAxis type", async () => { const schema = { ...mockPanelSchema, config: { mark_line: [ { name: "Upper Limit", type: "yAxis", value: 50, }, ], }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle mark lines without name", async () => { const schema = { ...mockPanelSchema, config: { mark_line: [ { type: "yAxis", value: 25, }, ], }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle multiple mark lines", async () => { const schema = { ...mockPanelSchema, config: { mark_line: [ { name: "Min", type: "yAxis", value: 5, }, { name: "Max", type: "yAxis", value: 100, }, ], }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle empty mark_line config", async () => { const schema = { ...mockPanelSchema, config: { mark_line: [], }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); }); describe("Missing value replacement", () => { it("should handle no_value_replacement config", async () => { const schema = { ...mockPanelSchema, config: { no_value_replacement: "N/A", }, }; const searchData = [ [ { timestamp: "2023-01-01", value: null }, { timestamp: "2023-01-02", value: 20 }, { timestamp: "2023-01-03", value: undefined }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle empty no_value_replacement config", async () => { const schema = { ...mockPanelSchema, config: { no_value_replacement: "", }, }; const searchData = [ [ { timestamp: "2023-01-01", value: null }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); }); describe("Time series and histogram functionality", () => { it("should handle time series data with histogram interval", async () => { const resultMetaData = [{ histogram_interval: 60 }]; const metadata = { queries: [ { startTime: "1640995200000", endTime: "1641081600000", }, ], }; const schema = { ...mockPanelSchema, type: "line", }; const searchData = [ [ { timestamp: 1640995200, value: 10 }, { timestamp: 1640998800, value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle time series data without histogram interval", async () => { const resultMetaData = [{}]; const metadata = { queries: [ { startTime: "1640995200000", endTime: "1641081600000", }, ], }; const schema = { ...mockPanelSchema, type: "line", }; const searchData = [ [ { timestamp: 1640995200, value: 10 }, { timestamp: 1640998800, value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); }); describe("Internal helper functions coverage", () => { it("should handle getXAxisKeys when no x fields exist", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: null, y: [{ alias: "value" }], breakdown: [], }, }, ], }; const result = await convertSQLData( schema, [[]], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); it("should handle getYAxisKeys when no y fields exist", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: null, breakdown: [], }, }, ], }; const result = await convertSQLData( schema, [[]], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); it("should handle getZAxisKeys when no z fields exist", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], z: null, breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle getBreakDownKeys when no breakdown fields exist", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: null, }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); }); describe("Warning message conditions", () => { it("should show warning when top_results exceeds max_dashboard_series and has many breakdown values", async () => { const storeWithLowLimit = { state: { zoConfig: { max_dashboard_series: 2, }, }, }; const schema = { ...mockPanelSchema, config: { top_results: 5, // Greater than max_dashboard_series top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100, category: "A" }, { timestamp: "2023-01-02", value: 90, category: "B" }, { timestamp: "2023-01-03", value: 80, category: "C" }, { timestamp: "2023-01-04", value: 70, category: "D" }, { timestamp: "2023-01-05", value: 60, category: "E" }, { timestamp: "2023-01-06", value: 50, category: "F" }, ], ]; const result = await convertSQLData( schema, searchData, storeWithLowLimit, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should show warning when no top_results but breakdown values exceed max_dashboard_series", async () => { const storeWithLowLimit = { state: { zoConfig: { max_dashboard_series: 2, }, }, }; const schema = { ...mockPanelSchema, config: { top_results: null, // No top_results top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100, category: "A" }, { timestamp: "2023-01-02", value: 90, category: "B" }, { timestamp: "2023-01-03", value: 80, category: "C" }, { timestamp: "2023-01-04", value: 70, category: "D" }, ], ]; const result = await convertSQLData( schema, searchData, storeWithLowLimit, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); }); describe("ProcessData edge cases", () => { it("should handle data where first element is not an array", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; // First element is not an array const searchData = [null]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); if (result.options) { expect(result.options.series).toBeDefined(); expect(Array.isArray(result.options.series)).toBe(true); } }); it("should handle breakdown value conversion to string", async () => { const schema = { ...mockPanelSchema, config: { top_results: 3, top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100, category: 123 }, // Number { timestamp: "2023-01-02", value: 90, category: true }, // Boolean { timestamp: "2023-01-03", value: 80, category: { nested: "object" } }, // Object ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle yAxis values with NaN and invalid numbers", async () => { const schema = { ...mockPanelSchema, config: { top_results: 3, top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: "invalid", category: "A" }, { timestamp: "2023-01-02", value: NaN, category: "B" }, { timestamp: "2023-01-03", value: Infinity, category: "C" }, { timestamp: "2023-01-04", value: -Infinity, category: "D" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); }); describe("Multi-dimensional axis handling", () => { it("should handle multiple x-axis fields", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }, { alias: "date" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", date: "Jan 1", value: 10 }, { timestamp: "2023-01-02", date: "Jan 2", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle multiple y-axis fields", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value1" }, { alias: "value2" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value1: 10, value2: 15 }, { timestamp: "2023-01-02", value1: 20, value2: 25 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle multiple z-axis fields", async () => { const schema = { ...mockPanelSchema, type: "heatmap", queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "category" }], z: [{ alias: "value1" }, { alias: "value2" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", category: "A", value1: 10, value2: 15 }, { timestamp: "2023-01-02", category: "B", value1: 20, value2: 25 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle multiple breakdown fields", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category1" }, { alias: "category2" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category1: "A", category2: "X" }, { timestamp: "2023-01-02", value: 20, category1: "B", category2: "Y" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); }); describe("Store configuration edge cases", () => { it("should handle undefined store state", async () => { const undefinedStore = { state: { theme: "light", zoConfig: { max_dashboard_series: 100, }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, undefinedStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); it("should handle null store", async () => { const nullStore = { state: { theme: "light", zoConfig: { max_dashboard_series: 100, }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, nullStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); it("should handle store with undefined zoConfig", async () => { const storeWithUndefinedConfig = { state: { theme: "light", zoConfig: undefined, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, storeWithUndefinedConfig, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); }); describe("Additional edge cases for complete coverage", () => { it("should handle data with zero breakdown key aggregation", async () => { const schema = { ...mockPanelSchema, config: { top_results: 3, top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 0, category: "A" }, { timestamp: "2023-01-02", value: 0, category: "B" }, { timestamp: "2023-01-03", value: 0, category: "C" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle negative values in breakdown aggregation", async () => { const schema = { ...mockPanelSchema, config: { top_results: 3, top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: -100, category: "A" }, { timestamp: "2023-01-02", value: -50, category: "B" }, { timestamp: "2023-01-03", value: -25, category: "C" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle empty string in xAxis value for others aggregation", async () => { const schema = { ...mockPanelSchema, config: { top_results: 1, top_results_others: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "", value: 100, category: "A" }, // High value - should be in top { timestamp: "", value: 50, category: "B" }, // Should go to others { timestamp: null, value: 25, category: "C" }, // Should go to others ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle array-like objects as breakdown values", async () => { const schema = { ...mockPanelSchema, config: { top_results: 3, top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100, category: [1, 2, 3] }, // Array { timestamp: "2023-01-02", value: 90, category: { length: 5 } }, // Array-like object { timestamp: "2023-01-03", value: 80, category: "normal" }, // Normal string ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle config with undefined top_results value", async () => { const schema = { ...mockPanelSchema, config: { top_results: undefined, top_results_others: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100, category: "A" }, { timestamp: "2023-01-02", value: 90, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle fields with empty alias arrays", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const result = await convertSQLData( schema, [[]], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); it("should handle fields with empty y alias arrays", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [], breakdown: [], }, }, ], }; const result = await convertSQLData( schema, [[]], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); }); it("should handle getAxisDataFromKey with filtered data", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: null, value: 20, category: "B" }, // Should be filtered out { timestamp: "2023-01-03", value: 30, category: "C" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle largestStackLabel calculation for stacked charts", async () => { const schema = { ...mockPanelSchema, type: "stacked", queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100, category: "A" }, { timestamp: "2023-01-02", value: 150, category: "A" }, // Max for A = 150 { timestamp: "2023-01-01", value: 200, category: "B" }, { timestamp: "2023-01-02", value: 50, category: "B" }, // Max for B = 200 ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle largestStackLabel with non-numeric values", async () => { const schema = { ...mockPanelSchema, type: "area-stacked", queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: "invalid", category: "A" }, { timestamp: "2023-01-02", value: 100, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle getPieChartRadius with layout dimensions", async () => { const schema = { ...mockPanelSchema, type: "pie", layout: { w: 5, // Width h: 4, // Height }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { category: "A", value: 10 }, { category: "B", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle getPieChartRadius with zero dimensions", async () => { const schema = { ...mockPanelSchema, type: "pie", layout: { w: 0, // Zero width h: 4, }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { category: "A", value: 10 }, { category: "B", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle getPieChartRadius without layout", async () => { const schema = { ...mockPanelSchema, type: "pie", layout: null, // No layout queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { category: "A", value: 10 }, { category: "B", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle annotation timestamp conversion", async () => { const annotationsWithTime = [ { value: [{ start_time: 1640995200000 }], // Unix timestamp in milliseconds }, ]; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, annotationsWithTime, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle annotations with null or undefined values", async () => { const annotationsWithNulls = [ { value: null, }, ]; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, annotationsWithNulls, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle maxValues calculation with unit formatting", async () => { const schema = { ...mockPanelSchema, config: { unit: "bytes", unit_custom: "", decimals: 2, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 1024 }, { timestamp: "2023-01-02", value: 2048 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle custom unit configurations", async () => { const schema = { ...mockPanelSchema, config: { unit: "custom", unit_custom: "req/sec", decimals: 1, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 1500.789 }, { timestamp: "2023-01-02", value: 2500.123 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle data with empty objects in keys calculation", async () => { const searchData = [ [ // Empty object should be handled gracefully {}, { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle maxValue validation with decimal numbers", async () => { const searchData = [ [ { timestamp: "2023-01-01", value: 123.45 }, { timestamp: "2023-01-02", value: 678.9 }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle maxValue with comma separators in formatted values", async () => { const schema = { ...mockPanelSchema, config: { unit: "auto", decimals: 0, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 1000000 }, // Should format to "1,000,000" { timestamp: "2023-01-02", value: 2000000 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle color mode calculations for non-heatmap charts", async () => { const schema = { ...mockPanelSchema, config: { color: { mode: "continuous", // Not in ColorModeWithoutMinMax }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 100 }, { timestamp: "2023-01-03", value: 50 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle heatmap color mode calculations", async () => { const schema = { ...mockPanelSchema, type: "heatmap", config: { color: { mode: "shades", // Not in ColorModeWithoutMinMax }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "category" }], z: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", category: "A", value: 10 }, { timestamp: "2023-01-02", category: "B", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle axis formatter error handling", async () => { const schema = { ...mockPanelSchema, config: { unit: "invalid", // This might cause formatUnitValue to throw unit_custom: "", decimals: "not_a_number", // Invalid decimals }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.yAxis).toBeDefined(); expect(typeof result.options.yAxis.axisLabel.formatter).toBe("function"); }); it("shows the y-axis name on wide panels and hides it on panels too narrow to fit it", async () => { const schema = { ...mockPanelSchema, queries: [ { ...mockPanelSchema.queries[0], fields: { ...mockPanelSchema.queries[0].fields, y: [{ alias: "value", label: "Data (Bytes)" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const convert = (offsetWidth: number) => convertSQLData( schema, searchData, mockStore, { value: { offsetWidth, offsetHeight: 400 } }, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); const wide = await convert(800); expect(wide.options.yAxis.name).toBe("Data (Bytes)"); // narrower than widest tick label + ~110px of name/margin/plot space const narrow = await convert(120); expect(narrow.options.yAxis.name).toBe(""); }); describe("axis labels", () => { const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const convert = (config: Record, labels = { x: "Day", y: "Requests" }) => convertSQLData( { ...mockPanelSchema, type: "bar", config: { ...mockPanelSchema.config, ...config }, queries: [ { ...mockPanelSchema.queries[0], fields: { ...mockPanelSchema.queries[0].fields, x: [{ alias: "timestamp", label: labels.x, column: "timestamp" }], y: [{ alias: "value", label: labels.y, column: "request_count" }], }, }, ], }, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); it("shows typed axis names unless the mode is hide", async () => { const hidden = await convert({ axis_label_mode: "hide" }); expect(hidden.options.xAxis[0].name).toBe(""); expect(hidden.options.yAxis.name).toBe(""); for (const config of [{ axis_label_mode: "auto" }, { axis_label_mode: "show" }, {}]) { const shown = await convert(config); expect(shown.options.xAxis[0].name).toBe("Day"); expect(shown.options.yAxis.name).toBe("Requests"); } }); it("names blank fields from the field but never puts that name on an axis", async () => { const blank = { x: "", y: "" }; for (const mode of ["auto", "show"]) { const chart = await convert({ axis_label_mode: mode }, blank); expect(chart.options.xAxis[0].name).toBe(""); expect(chart.options.yAxis.name).toBe(""); expect(chart.options.series[0].name).toBe("Request Count"); } }); it("leaves blank labels blank on a panel saved before the mode existed", async () => { const legacy = await convert({}, { x: "", y: "" }); expect(legacy.options.xAxis[0].name).toBe(""); expect(legacy.options.yAxis.name).toBe(""); expect(legacy.options.series[0].name).toBe(""); }); }); it("should handle horizontal bar charts (h-bar and h-stacked)", async () => { const hBarTypes = ["h-bar", "h-stacked"]; for (const chartType of hBarTypes) { const schema = { ...mockPanelSchema, type: chartType, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); } }); it("should handle axis border configuration", async () => { const schema = { ...mockPanelSchema, config: { axis_border_show: true, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.yAxis.axisLine.show).toBeDefined(); }); it("should handle empty search data with axis border", async () => { const searchData = [[]]; // Empty data const result = await convertSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.yAxis.axisLine.show).toBe(true); // Should show axis line when data is empty }); it("should handle toolbox visibility for different chart types", async () => { const hiddenToolboxTypes = ["pie", "donut", "metric", "gauge"]; for (const chartType of hiddenToolboxTypes) { const schema = { ...mockPanelSchema, type: chartType, }; const searchData = [ [ { category: "A", value: 10 }, { category: "B", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.toolbox.show).toBe(false); } }); it("should handle y-axis min/max configuration", async () => { const schema = { ...mockPanelSchema, config: { y_axis_min: 0, y_axis_max: 100, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 50 }, { timestamp: "2023-01-02", value: 75 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.yAxis.min).toBeDefined(); expect(result.options.yAxis.max).toBeDefined(); }); }); describe("Advanced coverage tests for uncovered branches", () => { it("should handle time-based data filling with breakdown keys", async () => { const resultMetaData = [{ histogram_interval: 60 }]; const metadata = { queries: [ { startTime: "1640995200000", // 2022-01-01 00:00:00 endTime: "1640995500000", // 2022-01-01 00:05:00 }, ], }; const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: 1640995200, value: 10, category: "A" }, { timestamp: 1640995320, value: 20, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle time filling with x-axis keys without timestamp", async () => { const resultMetaData = [{ histogram_interval: 60 }]; const metadata = { queries: [ { startTime: "1640995200000", // 2022-01-01 00:00:00 endTime: "1640995500000", // 2022-01-01 00:05:00 }, ], }; const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }, { alias: "location" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: 1640995200, location: "US", value: 10 }, { timestamp: 1640995320, location: "EU", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle time filling with no x-axis or breakdown keys", async () => { const resultMetaData = [{ histogram_interval: 60 }]; const metadata = { queries: [ { startTime: "1640995200000", endTime: "1640995500000", }, ], }; const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: 1640995200, value: 10 }, { timestamp: 1640995320, value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle trellis configuration for multiple series", async () => { const schema = { ...mockPanelSchema, config: { show_trellis: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, { timestamp: "2023-01-03", value: 30, category: "C" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle legend positioning configurations", async () => { const schema = { ...mockPanelSchema, config: { legends: { show: true, position: "bottom", }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); // Legend configuration is usually applied in the options but may not always exist as a separate property }); it("should handle series configuration with custom properties", async () => { const schema = { ...mockPanelSchema, config: { series: { symbol: "circle", symbolSize: 8, smooth: true, connectNulls: false, }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: null }, { timestamp: "2023-01-03", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle metric type charts", async () => { const schema = { ...mockPanelSchema, type: "metric", queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 100 }, { timestamp: "2023-01-02", value: 150 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle table type charts", async () => { const schema = { ...mockPanelSchema, type: "table", queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); // Table charts might not always have series if (result.options.series) { expect(Array.isArray(result.options.series)).toBe(true); } }); it("should handle chart configurations with different axis options", async () => { const schema = { ...mockPanelSchema, config: { x_axis: { type: "category", boundaryGap: false, inverse: true, }, y_axis: { type: "value", splitNumber: 5, logBase: 10, }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 1 }, { timestamp: "2023-01-02", value: 10 }, { timestamp: "2023-01-03", value: 100 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.xAxis).toBeDefined(); expect(result.options.yAxis).toBeDefined(); }); it("should handle annotation series identification", async () => { const annotationData = [ { value: [ { start_time: 1640995200000, end_time: 1640995500000, text: "Maintenance window", }, ], }, ]; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( mockPanelSchema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, annotationData, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle trellis configuration with valid panel dimensions", async () => { const schema = { ...mockPanelSchema, config: { show_trellis: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, { timestamp: "2023-01-03", value: 30, category: "C" }, { timestamp: "2023-01-04", value: 40, category: "D" }, ], ]; // Mock chartPanelRef with valid dimensions const validChartPanelRef = { value: { offsetWidth: 1200, offsetHeight: 800, }, }; const result = await convertSQLData( schema, searchData, mockStore, validChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle trellis configuration with invalid panel dimensions", async () => { const schema = { ...mockPanelSchema, config: { show_trellis: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, ], ]; // Mock chartPanelRef with invalid dimensions const invalidChartPanelRef = { value: { offsetWidth: 0, offsetHeight: 0, }, }; const result = await convertSQLData( schema, searchData, mockStore, invalidChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle trellis configuration with missing chartPanelRef", async () => { const schema = { ...mockPanelSchema, config: { show_trellis: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, ], ]; // Mock chartPanelRef as null/undefined const nullChartPanelRef = null; const result = await convertSQLData( schema, searchData, mockStore, nullChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); }); describe("Comprehensive coverage for remaining uncovered areas", () => { it("should handle time series data with histogram interval", async () => { const resultMetaData = [{ histogram_interval: 60 }]; const metadata = { queries: [ { startTime: "1640995200000", endTime: "1640995500000", // 5 minute span }, ], }; const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [[{ timestamp: 1640995200, value: 10 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle getLargestLabel calculation", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { category: "Very Long Category Name That Should Be Measured", value: 10 }, { category: "Short", value: 20 }, { category: "Another Very Long Category Name For Testing Purposes", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle complex series configuration with all properties", async () => { const schema = { ...mockPanelSchema, config: { connect_nulls: true, show_symbols: true, symbol_size: 10, smooth_lines: true, fill_area: true, stack_series: true, show_legend: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value1" }, { alias: "value2" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value1: 10, value2: 15, category: "A" }, { timestamp: "2023-01-02", value1: null, value2: 20, category: "A" }, { timestamp: "2023-01-03", value1: 30, value2: null, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle y-axis name gap calculation", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "very_long_metric_name_for_testing_purposes" }], breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", very_long_metric_name_for_testing_purposes: 10 }, { timestamp: "2023-01-02", very_long_metric_name_for_testing_purposes: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.yAxis).toBeDefined(); expect(result.options.yAxis.nameGap).toBeDefined(); }); it("should handle mark area configurations", async () => { const schema = { ...mockPanelSchema, config: { mark_area: [ { name: "High Load Period", x0: "2023-01-01", x1: "2023-01-02", y0: 50, y1: 100, }, ], }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 75 }, { timestamp: "2023-01-02", value: 80 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle different legend positions", async () => { const legendPositions = ["top", "bottom", "left", "right"]; for (const position of legendPositions) { const schema = { ...mockPanelSchema, config: { legends: { show: true, position: position, alignment: "center", }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options).toBeDefined(); } }); it("should handle scatter plot with z-axis values", async () => { const schema = { ...mockPanelSchema, type: "scatter", queries: [ { fields: { x: [{ alias: "x_value" }], y: [{ alias: "y_value" }], z: [{ alias: "size" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { x_value: 1, y_value: 10, size: 5, category: "A" }, { x_value: 2, y_value: 20, size: 8, category: "B" }, { x_value: 3, y_value: 15, size: 12, category: "C" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle empty series data gracefully", async () => { const schema = { ...mockPanelSchema, config: { show_trellis: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [[]]; // Empty data const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); }); }); describe("Final comprehensive coverage tests", () => { it("should handle getUniqueStackedXAxisValues function", async () => { const schema = { ...mockPanelSchema, type: "stacked", queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, { timestamp: "2023-01-03", value: 30, category: "A" }, { timestamp: "2023-01-04", value: 40, category: "C" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle single y-axis field label", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value", label: "Custom Y Label" }], // Single y field with label breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.yAxis.name).toBe("Custom Y Label"); }); it("should handle multiple y-axis fields without name", async () => { const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [ { alias: "value1", label: "Value 1" }, { alias: "value2", label: "Value 2" }, ], // Multiple y fields breakdown: [], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value1: 10, value2: 15 }, { timestamp: "2023-01-02", value1: 20, value2: 25 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.yAxis.name).toBe(""); // Should be empty for multiple fields }); }); describe("Additional convertMultiSQLData coverage", () => { it("should handle annotation series correctly", async () => { const schema = mockPanelSchema; const searchData = [ [{ timestamp: "2023-01-01", value: 10 }], [{ timestamp: "2023-01-02", value: 20 }], ]; const resultMetaData = { value: [[mockResultMetaData[0]], [mockResultMetaData[0]]] }; const metadata = { queries: [ { timeRangeGap: { periodAsStr: "1h" } }, { timeRangeGap: { periodAsStr: "2h" } }, ], }; const result = await convertMultiSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle multiple queries with series name transformations", async () => { // Create a schema that will produce series with names const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line" }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "A" }, ], [ { timestamp: "2023-01-01", value: 15, category: "B" }, { timestamp: "2023-01-02", value: 25, category: "B" }, ], ]; const resultMetaData = { value: [[mockResultMetaData[0]], [mockResultMetaData[0]]] }; const metadata = { queries: [ { timeRangeGap: { periodAsStr: "1h" } }, { timeRangeGap: { periodAsStr: "2h" } }, ], }; const result = await convertMultiSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("tags each query's series with its panel query and role, for right-click alerts", async () => { const searchData = [ [{ timestamp: "2023-01-01", value: 10 }], [{ timestamp: "2023-01-02", value: 20 }], ]; const resultMetaData = { value: [[mockResultMetaData[0]], [mockResultMetaData[0]]] }; const metadata = { queries: [ { panelQueryIndex: 0, timeRangeGap: { seconds: 0, periodAsStr: "" } }, { panelQueryIndex: 0, timeRangeGap: { seconds: 86_400_000, periodAsStr: "1d" } }, ], }; const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp", label: "Timestamp" }], y: [{ alias: "value", label: "Value" }], z: [], breakdown: [], }, }, ], }; const result = await convertMultiSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); const tagged = result.options.series.filter((s: any) => s._panelQueryIndex !== undefined); expect(tagged.map((s: any) => [s._panelQueryIndex, s._seriesRole])).toEqual([ [0, "primary"], [0, "shifted"], ]); }); it("should handle metadata queries without periodAsStr", async () => { const schema = mockPanelSchema; const searchData = [ [{ timestamp: "2023-01-01", value: 10 }], [{ timestamp: "2023-01-02", value: 20 }], ]; const resultMetaData = { value: [[mockResultMetaData[0]], [mockResultMetaData[0]]] }; const metadata = { queries: [ { timeRangeGap: {} }, // No periodAsStr { timeRangeGap: { periodAsStr: null } }, // Null periodAsStr ], }; const result = await convertMultiSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle processData with empty data or non-array first element", async () => { // Test the early return path in processData function (lines 228-229) const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, top_results: true, top_results_others: true }, }; // First test: empty data array let searchData = [[]]; let result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); // Second test: non-array first element searchData = [null]; // This will make data[0] null result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); }); it("should cover various missing value configurations", async () => { // Test lines around 410-411, 423-429, 433-481 (missing value logic) const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, no_value_replacement: "N/A", }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: null }, // This should trigger missing value logic ], ]; const resultMetaData = [ { interval: 3600000, // 1 hour in milliseconds }, ]; const metadata = { queries: [ { startTime: Date.now() - 86400000, // 24 hours ago endTime: Date.now(), }, ], }; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle chart type specific configurations", async () => { // Test various chart type configurations to hit uncovered branches const chartTypes = ["bar", "pie", "gauge", "metric", "table", "scatter"]; for (const chartType of chartTypes) { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: chartType, show_legends: true, legend_width: { value: 50 }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10 }, { timestamp: "2023-01-02", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); } }); it("should handle trellis configuration branches", async () => { // Test trellis configuration logic (lines 654-814) const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", trellis: { enable: true, layout: { rows: 2, cols: 2 }, group_by_y_axis: true, }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, ], ]; const chartPanelRef = { value: { offsetWidth: 800, offsetHeight: 600, }, }; const result = await convertSQLData( schema, searchData, mockStore, chartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); }); it("should cover processData early return with non-array data", async () => { // This test specifically targets lines 228-229 in processData const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, top_results: 5 }, }; // Create search data where the first element is not an array const searchData = [ "not_an_array", // This will trigger the early return in processData ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should cover missing value logic with time series data", async () => { // This test targets lines 410-411, 423-429, 433-481 in the missing value function const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "histogram", aggregationFunction: "histogram" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; // Create data with time gaps to trigger missing value logic const searchData = [ [ { histogram: 1640995200000, value: 10 }, // 2022-01-01 00:00:00 { histogram: 1640998800000, value: 20 }, // 2022-01-01 01:00:00 // Missing entry at 02:00:00 to trigger missing value filling { histogram: 1641006000000, value: 30 }, // 2022-01-01 03:00:00 ], ]; const resultMetaData = [ { interval: 3600, // 1 hour interval in seconds }, ]; const metadata = { queries: [ { startTime: 1640995200000, // 2022-01-01 00:00:00 endTime: 1641006000000, // 2022-01-01 03:00:00 }, ], }; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, resultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle scatter plot with z-axis configuration", async () => { // Target scatter plot specific logic const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "scatter" }, queries: [ { fields: { x: [{ alias: "x_field" }], y: [{ alias: "y_field" }], z: [{ alias: "z_field" }], // Z-axis for scatter plot breakdown: [], }, }, ], }; const searchData = [ [ { x_field: 10, y_field: 20, z_field: 5 }, { x_field: 15, y_field: 25, z_field: 8 }, { x_field: 20, y_field: 30, z_field: 12 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle gauge chart configuration", async () => { // Target gauge chart specific logic (lines around gauge configuration) const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "gauge" }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 75 }, { timestamp: "2023-01-02", value: 85 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should cover trellis configuration with proper conditions", async () => { // This specifically targets lines 654-814 (trellis configuration) const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", // Must be supported type for trellis trellis: { enable: true, layout: { rows: 2, cols: 2 }, group_by_y_axis: true, }, }, }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "A" }, { timestamp: "2023-01-02", value: 20, category: "B" }, { timestamp: "2023-01-03", value: 15, category: "A" }, ], ]; // Important: chartPanelRef with actual dimensions const chartPanelRefWithDimensions = { value: { offsetWidth: 1200, offsetHeight: 800, }, }; const result = await convertSQLData( schema, searchData, mockStore, chartPanelRefWithDimensions, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle table chart configuration", async () => { // Target table chart specific logic const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "table" }, }; const searchData = [ [ { name: "Item 1", value: 100, category: "A" }, { name: "Item 2", value: 200, category: "B" }, { name: "Item 3", value: 150, category: "A" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle metric chart configuration", async () => { // Target metric chart specific logic const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "metric" }, }; const searchData = [[{ timestamp: "2023-01-01", value: 1500 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle axis configuration and ordering", async () => { // Target axis configuration logic (lines 817-831, 839-902, 910-932) const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", axis: { x: { title: "X Axis Title" }, y: { title: "Y Axis Title" }, }, }, }; const searchData = [ [ { category: "A", value: 30 }, { category: "B", value: 20 }, { category: "C", value: 40 }, { category: "D", value: 10 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle time series tooltip formatting", async () => { // Target tooltip formatting logic (lines 2326-2512, 2527-2690) const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "timestamp", aggregationFunction: "histogram" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: 1640995200000, value: 10, category: "A" }, // Timestamp format { timestamp: 1640998800000, value: 20, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle breakdown with time series", async () => { // Target specific time series with breakdown logic const schema = { ...mockPanelSchema, queries: [ { fields: { x: [{ alias: "time_field" }], y: [{ alias: "value" }], breakdown: [{ alias: "series" }], }, }, ], }; const searchData = [ [ { time_field: "2023-01-01T00:00:00Z", value: 10, series: "metric1" }, { time_field: "2023-01-01T01:00:00Z", value: 20, series: "metric1" }, { time_field: "2023-01-01T00:00:00Z", value: 15, series: "metric2" }, { time_field: "2023-01-01T01:00:00Z", value: 25, series: "metric2" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle horizontal chart configuration", async () => { // Target horizontal chart logic const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "h-bar" }, }; const searchData = [ [ { category: "Category A", value: 100 }, { category: "Category B", value: 80 }, { category: "Category C", value: 120 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle pie chart with breakdown", async () => { // Target pie chart specific logic const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "pie" }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [], }, }, ], }; const searchData = [ [ { category: "Segment A", value: 30 }, { category: "Segment B", value: 45 }, { category: "Segment C", value: 25 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle heatmap chart configuration", async () => { // Target heatmap chart logic const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "heatmap" }, queries: [ { fields: { x: [{ alias: "x_coord" }], y: [{ alias: "y_coord" }], z: [{ alias: "intensity" }], }, }, ], }; const searchData = [ [ { x_coord: 0, y_coord: 0, intensity: 10 }, { x_coord: 0, y_coord: 1, intensity: 15 }, { x_coord: 1, y_coord: 0, intensity: 8 }, { x_coord: 1, y_coord: 1, intensity: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle stacked area chart", async () => { // Target stacked area chart logic const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "area", connect_nulls: true, stacked: true, }, queries: [ { fields: { x: [{ alias: "time" }], y: [{ alias: "value" }], breakdown: [{ alias: "stack_group" }], }, }, ], }; const searchData = [ [ { time: "2023-01-01", value: 10, stack_group: "Group A" }, { time: "2023-01-02", value: 15, stack_group: "Group A" }, { time: "2023-01-01", value: 20, stack_group: "Group B" }, { time: "2023-01-02", value: 25, stack_group: "Group B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle annotation series in convertMultiSQLData", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10 }, { timestamp: "2023-01-02T00:00:00Z", value: 20 }, ], ]; // Mock annotations that would trigger annotation series creation const mockAnnotationsWithSeries = { list: [ { id: "ann1", timeRange: { from: "2023-01-01T00:00:00Z", to: "2023-01-01T12:00:00Z" }, title: "Test Annotation", text: "Test annotation text", }, ], }; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotationsWithSeries, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle missing value interpolation with xAxisKeysWithoutTimeStamp", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", missing_values: { null_option: "null", show_line: false, interpolation: "linear", }, }, queries: [ { fields: { x: [{ alias: "timestamp" }, { alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", category: "A", value: 10 }, { timestamp: "2023-01-01T01:00:00Z", category: "B", value: 20 }, // Missing entry for timestamp: "2023-01-01T02:00:00Z", category: "A" { timestamp: "2023-01-01T03:00:00Z", category: "A", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle missing value interpolation with breakdownAxisKeysWithoutTimeStamp", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", missing_values: { null_option: "null", show_line: false, interpolation: "linear", }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", category: "A", value: 10 }, { timestamp: "2023-01-01T01:00:00Z", category: "B", value: 20 }, // Missing entry for timestamp: "2023-01-01T02:00:00Z", category: "A" { timestamp: "2023-01-01T03:00:00Z", category: "A", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle trellis configuration with custom layout", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", trellis: { layout: "custom", num_of_columns: 2, group_by_y_axis: false, }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10, category: "A" }, { timestamp: "2023-01-01T01:00:00Z", value: 20, category: "B" }, { timestamp: "2023-01-01T02:00:00Z", value: 30, category: "A" }, { timestamp: "2023-01-01T03:00:00Z", value: 40, category: "B" }, ], ]; // Mock chartPanelRef with valid dimensions const mockValidChartPanelRef = { value: { offsetWidth: 800, offsetHeight: 600, }, }; const result = await convertSQLData( schema, searchData, mockStore, mockValidChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); consoleSpy.mockRestore(); }); it("should handle trellis configuration with vertical layout", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", trellis: { layout: "vertical", group_by_y_axis: false, }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10, category: "A" }, { timestamp: "2023-01-01T01:00:00Z", value: 20, category: "B" }, ], ]; // Mock chartPanelRef with valid dimensions const mockValidChartPanelRef = { value: { offsetWidth: 800, offsetHeight: 600, }, }; const result = await convertSQLData( schema, searchData, mockStore, mockValidChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); consoleSpy.mockRestore(); }); it("should handle trellis configuration with group_by_y_axis enabled", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", trellis: { layout: "auto", group_by_y_axis: true, }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value1" }, { alias: "value2" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value1: 10, value2: 15, category: "A" }, { timestamp: "2023-01-01T01:00:00Z", value1: 20, value2: 25, category: "B" }, ], ]; // Mock chartPanelRef with valid dimensions const mockValidChartPanelRef = { value: { offsetWidth: 800, offsetHeight: 600, }, }; const result = await convertSQLData( schema, searchData, mockStore, mockValidChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); consoleSpy.mockRestore(); }); it("should handle Y-axis name gap calculation with horizontal chart", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "h-bar", unit: "bytes", unit_custom: null, decimals: 2, }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "Very Long Category Name That Should Affect Gap Calculation", value: 1000000, }, { category: "Short", value: 2000000 }, { category: "Another Category", value: 500000 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle Y-axis name gap calculation with vertical chart", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", unit: "percent", unit_custom: null, decimals: 1, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "percentage_with_very_long_name" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", percentage_with_very_long_name: 99.999 }, { timestamp: "2023-01-01T01:00:00Z", percentage_with_very_long_name: 88.888 }, { timestamp: "2023-01-01T02:00:00Z", percentage_with_very_long_name: 77.777 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); expect(result.options.yAxis).toBeDefined(); }); it("should handle tooltip formatting with histogram aggregation", async () => { const schema = { ...mockPanelSchema, queries: [ { customQuery: false, fields: { x: [ { alias: "timestamp", aggregationFunction: "histogram", column: "_timestamp", }, ], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00", value: 10 }, { timestamp: "2023-01-01T01:00:00", value: 20 }, { timestamp: "2023-01-01T02:00:00", value: 30 }, ], ]; // Mock store with timestamp column const mockStoreWithTimestamp = { ...mockStore, state: { ...mockStore.state, zoConfig: { timestamp_column: "_timestamp", }, }, }; const result = await convertSQLData( schema, searchData, mockStoreWithTimestamp, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, { ...mockMetadata, queries: [ { timeRangeGap: { seconds: 0 }, }, ], }, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle tooltip formatting with timestamp field without aggregation", async () => { const schema = { ...mockPanelSchema, queries: [ { customQuery: false, fields: { x: [ { alias: "timestamp", column: "_timestamp", // no aggregationFunction }, ], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10 }, { timestamp: "2023-01-01T01:00:00Z", value: 20 }, { timestamp: "2023-01-01T02:00:00Z", value: 30 }, ], ]; // Mock store with timestamp column const mockStoreWithTimestamp = { ...mockStore, state: { ...mockStore.state, zoConfig: { timestamp_column: "_timestamp", }, timezone: "UTC", }, }; const result = await convertSQLData( schema, searchData, mockStoreWithTimestamp, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, { ...mockMetadata, queries: [ { timeRangeGap: { seconds: 0 }, }, ], }, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle time series data formatting in tooltip", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; // Time series data that will trigger isTimeSeries check const searchData = [ [ { timestamp: "2023-01-01T00:00:00", value: 10 }, { timestamp: "2023-01-01T01:00:00", value: 20 }, { timestamp: "2023-01-01T02:00:00", value: 30 }, ], ]; const mockStoreWithTimezone = { ...mockStore, state: { ...mockStore.state, timezone: "America/New_York", }, }; const result = await convertSQLData( schema, searchData, mockStoreWithTimezone, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, { ...mockMetadata, queries: [ { timeRangeGap: { seconds: 3600 }, }, ], }, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle error with invalid trellis column configuration", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", trellis: { layout: "custom", num_of_columns: -1, // Invalid negative number }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [[{ timestamp: "2023-01-01T00:00:00Z", value: 10, category: "A" }]]; const mockValidChartPanelRef = { value: { offsetWidth: 800, offsetHeight: 600, }, }; const result = await convertSQLData( schema, searchData, mockStore, mockValidChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); consoleSpy.mockRestore(); }); it("should handle chart panel reference without dimensions", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", trellis: { layout: "auto", }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ timestamp: "2023-01-01T00:00:00Z", value: 10 }]]; // Mock chartPanelRef without dimensions (should trigger error handling) const mockInvalidChartPanelRef = { value: { offsetWidth: 0, offsetHeight: 0, }, }; const result = await convertSQLData( schema, searchData, mockStore, mockInvalidChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle missing chart panel reference", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", trellis: { layout: "auto", }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ timestamp: "2023-01-01T00:00:00Z", value: 10 }]]; // Mock null chartPanelRef (should trigger error handling) const mockNullChartPanelRef = { value: null, }; const result = await convertSQLData( schema, searchData, mockStore, mockNullChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle wordcloud chart type", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "wordcloud", }, queries: [ { fields: { x: [{ alias: "word" }], y: [{ alias: "frequency" }], }, }, ], }; const searchData = [ [ { word: "test", frequency: 10 }, { word: "data", frequency: 20 }, { word: "visualization", frequency: 15 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle sankey chart type", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "sankey", }, queries: [ { fields: { source: { alias: "source_field" }, target: { alias: "target_field" }, value: { alias: "value_field" }, }, }, ], }; const searchData = [ [ { source_field: "A", target_field: "B", value_field: 10 }, { source_field: "B", target_field: "C", value_field: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle geomap chart type", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "geomap", }, queries: [ { fields: { latitude: { alias: "lat" }, longitude: { alias: "lng" }, weight: { alias: "weight" }, }, }, ], }; const searchData = [ [ { lat: 40.7128, lng: -74.006, weight: 100 }, { lat: 34.0522, lng: -118.2437, weight: 200 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle custom query mode with time shift", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { customQuery: true, query: "SELECT timestamp, value FROM logs", fields: { x: [{ alias: "timestamp", column: "_timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10 }, { timestamp: "2023-01-01T01:00:00Z", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, { ...mockStore, state: { ...mockStore.state, zoConfig: { timestamp_column: "_timestamp", }, }, }, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle null/undefined values in breakdown data", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: null }, { timestamp: "2023-01-02", value: 20, category: undefined }, { timestamp: "2023-01-03", value: 30, category: "A" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle empty string values in data", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "", value: 10, category: "A" }, { timestamp: "2023-01-02", value: "", category: "" }, { timestamp: "2023-01-03", value: 30, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle extremely large dataset with performance considerations", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", top_results: 1000, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; // Create large dataset const largeData = Array.from({ length: 1000 }, (_, i) => ({ timestamp: `2023-01-01T${String(i % 24).padStart(2, "0")}:${String((i * 5) % 60).padStart(2, "0")}:00Z`, value: Math.random() * 1000, })); const searchData = [largeData]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle treemap chart type with nested data", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "treemap", }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [{ alias: "subcategory" }], }, }, ], }; const searchData = [ [ { category: "Technology", value: 100, subcategory: "Software" }, { category: "Technology", value: 80, subcategory: "Hardware" }, { category: "Finance", value: 120, subcategory: "Banking" }, { category: "Finance", value: 90, subcategory: "Investment" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle complex unit configurations and formatting", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", unit: "custom", unit_custom: "req/sec", decimals: 3, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "requests_per_second" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", requests_per_second: 123.456789 }, { timestamp: "2023-01-01T01:00:00Z", requests_per_second: 987.654321 }, { timestamp: "2023-01-01T02:00:00Z", requests_per_second: 555.123456 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle tooltip formatter error handling (lines 2503-2509)", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10 }, { timestamp: "invalid-date", value: 20 }, // This will trigger error handling in formatter ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.tooltip).toBeDefined(); expect(result.options.tooltip.formatter).toBeDefined(); // Test the formatter function directly with malformed data const formatter = result.options.tooltip.formatter; const malformedParams = [{ value: [null] }]; const formattedResult = formatter(malformedParams); expect(typeof formattedResult).toBe("string"); // Should be a string (may contain HTML) }); it("should handle time series data mapping with null values (lines 2538-2689)", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; // Create time series data that will trigger isTimeSeriesData path const searchData = [ [ { timestamp: "2023-01-01T00:00:00", value: null }, // null value to test handling { timestamp: "2023-01-01T01:00:00", value: 20 }, { timestamp: "2023-01-01T02:00:00", value: undefined }, // undefined to test handling ], ]; const mockStoreWithTimezone = { ...mockStore, state: { ...mockStore.state, timezone: "UTC", }, }; const result = await convertSQLData( schema, searchData, mockStoreWithTimezone, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, { ...mockMetadata, queries: [ { timeRangeGap: { seconds: 0 }, }, ], }, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); // Verify that the series data has been transformed to time series format if (result.options.series && result.options.series.length > 0) { expect(result.options.series[0].data).toBeDefined(); } }); it("should handle Y-axis ordering and total calculations (lines 2713-2736)", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", axis: { y_axis: { order_by_series: true, }, }, }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value1" }, { alias: "value2" }], }, }, ], }; const searchData = [ [ { category: "A", value1: 10, value2: 15 }, { category: "B", value1: 20, value2: 25 }, { category: "C", value1: 30, value2: 35 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); expect(result.options.xAxis).toBeDefined(); }); it("should handle series with breakdown and data transformation (lines 2743-2784)", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", connect_nulls: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "status" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00", value: 10, status: "success" }, { timestamp: "2023-01-01T01:00:00", value: null, status: "success" }, // null value { timestamp: "2023-01-01T02:00:00", value: 30, status: "error" }, { timestamp: "2023-01-01T03:00:00", value: 40, status: "error" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(1); // Should have multiple series for breakdown }); it("should handle specific chart configurations (lines 2849, 2851, 2874-2876)", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "area", connect_nulls: true, show_symbols: false, smooth_lines: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10 }, { timestamp: "2023-01-01T01:00:00Z", value: 20 }, { timestamp: "2023-01-01T02:00:00Z", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle special chart type configurations (lines 2903, 2971, 3009)", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", show_legend: true, legends: { show: true, position: "right", alignment: "middle", }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "series_name" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, series_name: "Series A" }, { timestamp: "2023-01-02", value: 20, series_name: "Series B" }, { timestamp: "2023-01-03", value: 30, series_name: "Series A" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); expect(result.options.legend).toBeDefined(); }); it("should handle top_results warning when series exceed max_dashboard_series", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", top_results: 150, // Greater than max_dashboard_series }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; // Create data that exceeds the max series limit const searchData = [ Array.from({ length: 160 }, (_, i) => ({ category: `Category_${i}`, value: Math.random() * 100, })), ]; const mockStoreWithMaxSeries = { ...mockStore, state: { ...mockStore.state, zoConfig: { max_dashboard_series: 100, }, }, }; const result = await convertSQLData( schema, searchData, mockStoreWithMaxSeries, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle series limit warning when no top_results specified", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", // No top_results specified }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [{ alias: "breakdown" }], }, }, ], }; // Create data that exceeds max_dashboard_series const searchData = [ Array.from({ length: 120 }, (_, i) => ({ category: `Category_${i % 10}`, breakdown: `Breakdown_${i}`, value: Math.random() * 100, })), ]; const mockStoreWithMaxSeries = { ...mockStore, state: { ...mockStore.state, zoConfig: { max_dashboard_series: 100, }, }, }; const result = await convertSQLData( schema, searchData, mockStoreWithMaxSeries, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle empty and undefined breakdown values properly", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, category: "" }, // Empty string { timestamp: "2023-01-02", value: 20, category: null }, // null { timestamp: "2023-01-03", value: 30, category: undefined }, // undefined { timestamp: "2023-01-04", value: 40, category: "Valid Category" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle processData with top_results_others enabled", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", top_results: 3, top_results_others: true, }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [{ alias: "breakdown" }], }, }, ], }; const searchData = [ [ { category: "Cat1", value: 100, breakdown: "A" }, { category: "Cat2", value: 90, breakdown: "B" }, { category: "Cat3", value: 80, breakdown: "C" }, { category: "Cat4", value: 70, breakdown: "D" }, // Should go to "Others" { category: "Cat5", value: 60, breakdown: "E" }, // Should go to "Others" ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle isTimeStamp data path in time series logic", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; // Use timestamp format that triggers isTimeStamp instead of isTimeSeries const searchData = [ [ { timestamp: 1672531200000, value: 10 }, // Unix timestamp { timestamp: 1672534800000, value: 20 }, { timestamp: 1672538400000, value: 30 }, ], ]; const mockStoreWithTimezone = { ...mockStore, state: { ...mockStore.state, timezone: "Europe/London", }, }; const result = await convertSQLData( schema, searchData, mockStoreWithTimezone, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, { ...mockMetadata, queries: [ { timeRangeGap: { seconds: 3600 }, }, ], }, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle complex nested breakdown scenarios", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "cpu_usage" }, { alias: "memory_usage" }], breakdown: [{ alias: "host" }, { alias: "service" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", cpu_usage: 85, memory_usage: 70, host: "server1", service: "web", }, { timestamp: "2023-01-01T01:00:00Z", cpu_usage: 90, memory_usage: 75, host: "server2", service: "api", }, { timestamp: "2023-01-01T02:00:00Z", cpu_usage: 78, memory_usage: 68, host: "server1", service: "db", }, { timestamp: "2023-01-01T03:00:00Z", cpu_usage: 82, memory_usage: 72, host: "server2", service: "web", }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(1); }); it("should handle chart with mark areas and regions", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", mark_area: [ { name: "Peak Hours", yAxis: [100, 200], }, ], mark_line: [ { name: "Threshold", yAxis: 150, }, ], }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 100 }, { timestamp: "2023-01-01T01:00:00Z", value: 150 }, { timestamp: "2023-01-01T02:00:00Z", value: 120 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle color configuration with palette-classic mode", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", color: { mode: "palette-classic", colorBySeries: false, }, }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "A", value: 10 }, { category: "B", value: 20 }, { category: "C", value: 30 }, ], ]; const mockStoreWithTheme = { ...mockStore, state: { ...mockStore.state, theme: "dark", }, }; const result = await convertSQLData( schema, searchData, mockStoreWithTheme, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle color configuration with custom color mode", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", color: { mode: "custom", colorBySeries: true, colors: ["#FF6B6B", "#4ECDC4", "#45B7D1"], }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "series" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, series: "Series1" }, { timestamp: "2023-01-02", value: 20, series: "Series2" }, { timestamp: "2023-01-03", value: 30, series: "Series3" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle Y-axis configuration with single field label", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", y_axis_min: 0, y_axis_max: 100, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "cpu_usage", label: "CPU Usage (%)" }], // Single field with label }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", cpu_usage: 45 }, { timestamp: "2023-01-01T01:00:00Z", cpu_usage: 67 }, { timestamp: "2023-01-01T02:00:00Z", cpu_usage: 89 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.yAxis).toBeDefined(); expect(result.options.yAxis.name).toBe("CPU Usage (%)"); expect(result.options.yAxis.min).toBe(0); expect(result.options.yAxis.max).toBeGreaterThan(0); }); it("should handle horizontal bar chart Y-axis calculations", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "h-bar", }, queries: [ { fields: { x: [{ alias: "very_long_category_name_for_testing" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { very_long_category_name_for_testing: "Very Long Category Name That Should Affect Layout", value: 100, }, { very_long_category_name_for_testing: "Another Very Long Category", value: 200 }, { very_long_category_name_for_testing: "Short", value: 150 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.yAxis).toBeDefined(); expect(result.options.yAxis.nameGap).toBeDefined(); }); it("should handle horizontal stacked bar chart configuration", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "h-stacked", }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value1" }, { alias: "value2" }], }, }, ], }; const searchData = [ [ { category: "Category A", value1: 10, value2: 15 }, { category: "Category B", value1: 20, value2: 25 }, { category: "Category C", value1: 30, value2: 35 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBe(2); // Two Y-axis fields }); it("should handle missing value interpolation with connect_nulls disabled", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", connect_nulls: false, missing_values: { null_option: "null", show_line: false, }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10 }, { timestamp: "2023-01-01T01:00:00Z", value: null }, { timestamp: "2023-01-01T02:00:00Z", value: 30 }, { timestamp: "2023-01-01T03:00:00Z", value: undefined }, { timestamp: "2023-01-01T04:00:00Z", value: 50 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series[0].connectNulls).toBe(false); }); it("should handle chart with complex legend configuration", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", show_legend: true, legends: { show: true, position: "bottom", alignment: "center", wrap: true, }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "service_name" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01", value: 10, service_name: "Authentication Service" }, { timestamp: "2023-01-02", value: 20, service_name: "Payment Processing Service" }, { timestamp: "2023-01-03", value: 30, service_name: "Notification Service" }, { timestamp: "2023-01-04", value: 40, service_name: "User Management Service" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.legend).toBeDefined(); }); it("should handle chart with custom zoom configuration", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", zoom: { enabled: true, type: "xy", }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 100 }, { timestamp: "2023-01-01T01:00:00Z", value: 150 }, { timestamp: "2023-01-01T02:00:00Z", value: 120 }, { timestamp: "2023-01-01T03:00:00Z", value: 180 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle data with extremely large numbers", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", unit: "bytes", decimals: 0, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "bytes_processed" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", bytes_processed: 9999999999999 }, // Very large number { timestamp: "2023-01-01T01:00:00Z", bytes_processed: 1234567890123 }, { timestamp: "2023-01-01T02:00:00Z", bytes_processed: 987654321098 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle data with negative values and zero", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "profit_loss" }], }, }, ], }; const searchData = [ [ { category: "Q1", profit_loss: -1500 }, { category: "Q2", profit_loss: 0 }, { category: "Q3", profit_loss: 2500 }, { category: "Q4", profit_loss: -800 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle area-stacked chart with line interpolation", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "area-stacked", line_interpolation: "smooth", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "cpu_usage" }, { alias: "memory_usage" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", cpu_usage: 45, memory_usage: 60 }, { timestamp: "2023-01-01T01:00:00Z", cpu_usage: 50, memory_usage: 65 }, { timestamp: "2023-01-01T02:00:00Z", cpu_usage: 55, memory_usage: 70 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBe(2); }); it("should handle step line interpolation types", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", line_interpolation: "step-start", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10 }, { timestamp: "2023-01-01T01:00:00Z", value: 20 }, { timestamp: "2023-01-01T02:00:00Z", value: 15 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series[0].step).toBe("start"); }); it("should handle heatmap chart with visualMap configuration", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "heatmap", }, queries: [ { fields: { x: [{ alias: "hour" }], y: [{ alias: "day" }], z: [{ alias: "temperature" }], }, }, ], }; const searchData = [ [ { hour: 0, day: "Monday", temperature: 22 }, { hour: 1, day: "Monday", temperature: 20 }, { hour: 0, day: "Tuesday", temperature: 25 }, { hour: 1, day: "Tuesday", temperature: 23 }, { hour: 0, day: "Wednesday", temperature: 24 }, { hour: 1, day: "Wednesday", temperature: 22 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle table chart type with formatted data", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "table", }, queries: [ { fields: { x: [{ alias: "service" }], y: [{ alias: "requests" }, { alias: "errors" }], }, }, ], }; const searchData = [ [ { service: "auth-service", requests: 1500, errors: 5 }, { service: "payment-service", requests: 800, errors: 2 }, { service: "user-service", requests: 2000, errors: 8 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle donut chart configuration", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "donut", }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "Desktop", value: 45 }, { category: "Mobile", value: 35 }, { category: "Tablet", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle radar chart with multiple dimensions", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "radar", }, queries: [ { fields: { x: [{ alias: "metric" }], y: [{ alias: "value" }], breakdown: [{ alias: "team" }], }, }, ], }; const searchData = [ [ { metric: "Performance", value: 85, team: "Backend" }, { metric: "Reliability", value: 92, team: "Backend" }, { metric: "Security", value: 78, team: "Backend" }, { metric: "Performance", value: 88, team: "Frontend" }, { metric: "Reliability", value: 90, team: "Frontend" }, { metric: "Security", value: 82, team: "Frontend" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle data with special characters and unicode", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", }, queries: [ { fields: { x: [{ alias: "region" }], y: [{ alias: "sales" }], }, }, ], }; const searchData = [ [ { region: "北京", sales: 150000 }, // Chinese characters { region: "São Paulo", sales: 120000 }, // Portuguese characters { region: "München", sales: 98000 }, // German characters { region: "العربية", sales: 87000 }, // Arabic characters { region: "Москва", sales: 105000 }, // Russian characters ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle mixed data types and edge values", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "scatter", }, queries: [ { fields: { x: [{ alias: "x_value" }], y: [{ alias: "y_value" }], }, }, ], }; const searchData = [ [ { x_value: 0, y_value: 0 }, // Zero values { x_value: Infinity, y_value: 100 }, // Infinity { x_value: -Infinity, y_value: 50 }, // Negative infinity { x_value: NaN, y_value: 75 }, // NaN { x_value: 1.23456789e-10, y_value: 2.98765432e10 }, // Very small and large numbers { x_value: "", y_value: "not_a_number" }, // String values ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle complex time series with gaps", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", missing_values: { null_option: "interpolate", interpolation: "linear", show_line: true, }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "metric" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", metric: 100 }, { timestamp: "2023-01-01T00:05:00Z", metric: 110 }, // Gap: missing 00:10:00 { timestamp: "2023-01-01T00:15:00Z", metric: 130 }, { timestamp: "2023-01-01T00:20:00Z", metric: null }, // Explicit null { timestamp: "2023-01-01T00:25:00Z", metric: 150 }, // Gap: missing 00:30:00 ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, { ...mockMetadata, queries: [ { timeRangeGap: { seconds: 300 }, // 5 minute gaps }, ], }, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle multiple queries with different data structures", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "cpu" }], }, }, { fields: { x: [{ alias: "time" }], // Different alias y: [{ alias: "memory" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", cpu: 45 }, { timestamp: "2023-01-01T01:00:00Z", cpu: 50 }, ], [ { time: "2023-01-01T00:00:00Z", memory: 65 }, { time: "2023-01-01T01:00:00Z", memory: 70 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle line chart with specific symbol and thickness configurations", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", show_symbol: true, line_thickness: 3, line_interpolation: "step-middle", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10 }, { timestamp: "2023-01-01T01:00:00Z", value: 20 }, { timestamp: "2023-01-01T02:00:00Z", value: 15 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series[0].showSymbol).toBe(true); expect(result.options.series[0].step).toBe("middle"); }); it("should handle scatter chart with custom symbol size", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "scatter", }, queries: [ { fields: { x: [{ alias: "x_coord" }], y: [{ alias: "y_coord" }], }, }, ], }; const searchData = [ [ { x_coord: 1.5, y_coord: 2.8 }, { x_coord: 3.2, y_coord: 4.1 }, { x_coord: 5.7, y_coord: 1.9 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series[0].type).toBe("line"); expect(result.options.series[0]).toBeDefined(); }); it("should handle metadata queries with start time and interval processing", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 100 }, { timestamp: "2023-01-01T00:15:00Z", value: 110 }, { timestamp: "2023-01-01T00:30:00Z", value: 105 }, ], ]; const metadataWithStartTime = { ...mockMetadata, queries: [ { startTime: 1672531200000, // Unix timestamp in milliseconds timeRangeGap: { seconds: 900 }, // 15 minutes }, ], }; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, metadataWithStartTime, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle chart type exclusion for missing value processing", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "pie", // Type not in ["area-stacked", "line", "area", "bar", "stacked"] }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "A", value: 30 }, { category: "B", value: null }, // This should be processed differently for pie charts { category: "C", value: 50 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, { ...mockMetadata, queries: [{ timeRangeGap: { seconds: 300 } }], }, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle advanced missing value scenarios with interpolation", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "area", missing_values: { null_option: "interpolate", interpolation: "linear", }, }, queries: [ { fields: { x: [{ alias: "time_key" }], y: [{ alias: "metric_value" }], }, }, ], }; const searchData = [ [ { time_key: "2023-01-01T00:00:00Z", metric_value: 100 }, { time_key: "2023-01-01T00:05:00Z", metric_value: null }, // Missing value { time_key: "2023-01-01T00:10:00Z", metric_value: 120 }, { time_key: "2023-01-01T00:15:00Z", metric_value: null }, // Another missing value { time_key: "2023-01-01T00:20:00Z", metric_value: 140 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, { ...mockMetadata, queries: [{ timeRangeGap: { seconds: 300 } }], }, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle custom chart with JavaScript execution", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "custom", }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "Custom A", value: 25 }, { category: "Custom B", value: 35 }, { category: "Custom C", value: 40 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); // Custom charts may return different structure expect(result.options || result).toBeDefined(); }); it("should handle empty annotation list", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 10 }, { timestamp: "2023-01-01T01:00:00Z", value: 20 }, ], ]; const emptyAnnotations = { list: [], // Empty annotations list }; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, emptyAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle data with boolean values", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", }, queries: [ { fields: { x: [{ alias: "feature" }], y: [{ alias: "enabled" }], }, }, ], }; const searchData = [ [ { feature: "Feature A", enabled: true }, { feature: "Feature B", enabled: false }, { feature: "Feature C", enabled: true }, { feature: "Feature D", enabled: 1 }, // Truthy number { feature: "Feature E", enabled: 0 }, // Falsy number ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle time-based key identification in missing value processing", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "stacked", }, queries: [ { fields: { x: [{ alias: "datetime" }], // Time-based key y: [{ alias: "count" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { datetime: "2023-01-01T00:00:00Z", count: 10, category: "A" }, { datetime: "2023-01-01T00:05:00Z", count: 15, category: "B" }, { datetime: "2023-01-01T00:10:00Z", count: null, category: "A" }, // Missing value { datetime: "2023-01-01T00:15:00Z", count: 20, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, { ...mockMetadata, queries: [{ timeRangeGap: { seconds: 300 } }], }, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle date bin calculations with complex intervals", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", }, queries: [ { fields: { x: [{ alias: "bin_time" }], y: [{ alias: "count" }], }, }, ], }; const searchData = [ [ { bin_time: "2023-01-01T00:00:00Z", count: 5 }, { bin_time: "2023-01-01T01:00:00Z", count: 8 }, { bin_time: "2023-01-01T02:00:00Z", count: 12 }, ], ]; const metadataWithComplexInterval = { ...mockMetadata, queries: [ { startTime: 1672531200000, timeRangeGap: { seconds: 3600 }, // 1 hour interval }, ], }; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, metadataWithComplexInterval, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle tooltip axis pointer configuration", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", axis_pointer: "shadow", connect_nulls: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "count" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", count: 150 }, { timestamp: "2023-01-01T01:00:00Z", count: null }, { timestamp: "2023-01-01T02:00:00Z", count: 250 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.tooltip.axisPointer.type).toBe("cross"); expect(result.options.series[0].connectNulls).toBe(true); }); it("should handle time series with different x-axis types", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", axis_border_show: false, axis_label_rotation: 45, }, queries: [ { fields: { x: [{ alias: "timestamp", aggregationFunction: "histogram" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: 1672531200000, value: 100 }, { timestamp: 1672534800000, value: 200 }, { timestamp: 1672538400000, value: 150 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.xAxis[0].type).toBe("category"); expect(result.options.xAxis[0].axisLabel).toBeDefined(); }); it("should handle legend scroll configuration with many series", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", legend_position: "bottom", legend_orientation: "horizontal", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [[]]; // Generate many series to trigger scroll for (let i = 0; i < 25; i++) { searchData[0].push({ timestamp: "2023-01-01T00:00:00Z", value: Math.random() * 100, category: `Series_${i}`, }); } const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.legend).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle data zoom configuration for large datasets", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", show_data_zoom: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[]]; // Generate large dataset to trigger data zoom for (let i = 0; i < 200; i++) { searchData[0].push({ timestamp: new Date(2023, 0, 1, i % 24, 0).toISOString(), value: Math.random() * 100, }); } const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle brush selection configuration", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "scatter", enable_brush: true, }, queries: [ { fields: { x: [{ alias: "x_val" }], y: [{ alias: "y_val" }], }, }, ], }; const searchData = [ [ { x_val: 1, y_val: 10 }, { x_val: 2, y_val: 20 }, { x_val: 3, y_val: 15 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series[0].type).toBe("line"); }); it("should handle visualization map configuration with custom colors", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "heatmap", color_scheme: "custom", custom_colors: ["#ff0000", "#00ff00", "#0000ff"], }, queries: [ { fields: { x: [{ alias: "x_axis" }], y: [{ alias: "y_axis" }], z: [{ alias: "intensity" }], }, }, ], }; const searchData = [ [ { x_axis: "A", y_axis: "1", intensity: 5 }, { x_axis: "B", y_axis: "2", intensity: 10 }, { x_axis: "C", y_axis: "3", intensity: 15 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series[0].type).toBe("line"); }); it("should handle complex axis formatting with scientific notation", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", scientific_notation: true, axis_width: 200, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "large_value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", large_value: 1.23e10 }, { timestamp: "2023-01-01T01:00:00Z", large_value: 5.67e12 }, { timestamp: "2023-01-01T02:00:00Z", large_value: 9.87e8 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series[0].type).toBe("line"); }); it("should handle grid configuration with multiple y-axes", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", show_grid: true, grid_left_margin: 60, grid_right_margin: 60, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "metric1" }, { alias: "metric2" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", metric1: 100, metric2: 0.5 }, { timestamp: "2023-01-01T01:00:00Z", metric1: 150, metric2: 0.8 }, { timestamp: "2023-01-01T02:00:00Z", metric1: 200, metric2: 1.2 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.grid.left).toBeGreaterThan(5); expect(result.options.grid.right).toBe(20); expect(result.options.yAxis).toBeDefined(); }); it("should handle animation and transition configurations", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", enable_animation: true, animation_delay: 100, animation_duration: 1000, }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "A", value: 100 }, { category: "B", value: 200 }, { category: "C", value: 150 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series[0].type).toBe("line"); }); it("should handle error in tooltip formatter gracefully", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ timestamp: "2023-01-01T00:00:00Z", value: 100 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(typeof result.options.tooltip.formatter).toBe("function"); // Test error handling by passing invalid params const tooltipResult = result.options.tooltip.formatter(null); expect(typeof tooltipResult).toBe("string"); }); it("should handle trellis configuration errors", async () => { const mockChartPanelRefWithError = { value: null, // This will cause an error in trellis config }; const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", trellis: { enable: true, num_of_columns: 2, }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 100, category: "A" }, { timestamp: "2023-01-01T01:00:00Z", value: 200, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRefWithError, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); // Should still work despite trellis error expect(result.options.series).toBeDefined(); }); it("should handle invalid trellis column configuration", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", trellis: { enable: true, num_of_columns: 0, // Invalid column count }, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [[{ timestamp: "2023-01-01T00:00:00Z", value: 100, category: "A" }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle axis formatter errors gracefully", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", unit: "invalid_unit", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ timestamp: "2023-01-01T00:00:00Z", value: 100 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(typeof result.options.yAxis.axisLabel.formatter).toBe("function"); // Test error handling in axis formatter const formatterResult = result.options.yAxis.axisLabel.formatter(null); expect(formatterResult).toBeDefined(); }); it("should handle pie chart tooltip formatter errors", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "pie", }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "A", value: 100 }, { category: "B", value: 200 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(typeof result.options.tooltip.formatter).toBe("function"); // Test error handling by passing invalid params const tooltipResult = result.options.tooltip.formatter(null); expect(typeof tooltipResult).toBe("string"); }); it("should handle donut chart tooltip formatter errors", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "donut", }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "A", value: 100 }, { category: "B", value: 200 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(typeof result.options.tooltip.formatter).toBe("function"); // Test error handling by passing invalid params const tooltipResult = result.options.tooltip.formatter(null); expect(typeof tooltipResult).toBe("string"); }); it("should handle heatmap tooltip formatter errors", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "heatmap", }, queries: [ { fields: { x: [{ alias: "x_axis" }], y: [{ alias: "y_axis" }], z: [{ alias: "intensity" }], }, }, ], }; const searchData = [ [ { x_axis: "A", y_axis: "1", intensity: 5 }, { x_axis: "B", y_axis: "2", intensity: 10 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(typeof result.options.tooltip.formatter).toBe("function"); // Test error handling by passing invalid params const tooltipResult = result.options.tooltip.formatter(null); expect(typeof tooltipResult).toBe("string"); }); it("should handle gauge chart detail formatter errors", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "gauge", }, queries: [ { fields: { y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ value: 75 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); if (result.options && result.options.series && result.options.series[0]) { expect(result.options.series[0].detail.formatter).toBeDefined(); } // Test error handling by passing invalid params if (result.options && result.options.series && result.options.series[0]) { const formatterResult = result.options.series[0].detail.formatter(null); expect(typeof formatterResult).toBe("string"); } }); it("should handle metric chart render item errors", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "metric", }, queries: [ { fields: { y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ value: 12345 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); if (result.options && result.options.series && result.options.series[0]) { expect(result.options.series[0].renderItem).toBeDefined(); // Test error handling by passing invalid params const renderResult = result.options.series[0].renderItem(null); expect(typeof renderResult).toBe("string"); } }); it("should handle time series data with timestamp fields", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const metadata = { histogramInterval: "1h", dateTimeStartPoint: new Date("2023-01-01T00:00:00Z").getTime(), timestampField: "timestamp", }; const searchData = [ [ { timestamp: 1672531200000, value: 100 }, { timestamp: 1672534800000, value: 200 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.xAxis[0].type).toBe("category"); }); it("should handle switch default case", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "unknown_chart_type", // Will trigger default case }, queries: [ { fields: { x: [{ alias: "x_field" }], y: [{ alias: "y_field" }], }, }, ], }; const searchData = [ [ { x_field: "A", y_field: 100 }, { x_field: "B", y_field: 200 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle top_results_others configuration", async () => { const storeWithTopResults = { ...mockStore, state: { ...mockStore.state, zoConfig: { ...mockStore.state.zoConfig, max_dashboard_series: 5, }, }, }; const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", top_results: 3, top_results_others: true, }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [{ alias: "group" }], }, }, ], }; const searchData = [[]]; // Generate data that will trigger top_results_others logic for (let i = 0; i < 10; i++) { searchData[0].push({ category: `Cat_${i}`, value: Math.random() * 100, group: `Group_${i % 3}`, }); } const result = await convertSQLData( schema, searchData, storeWithTopResults, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle console.error paths in formatters", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ timestamp: "invalid_date", value: "invalid_number" }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); // Test tooltip formatter with invalid data if (typeof result.options.tooltip.formatter === "function") { const tooltipResult = result.options.tooltip.formatter([{ value: ["invalid", NaN] }]); expect(typeof tooltipResult).toBe("string"); } }); it("should handle valueFormatter errors in gauge chart", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "gauge", unit: "invalid_unit", }, queries: [ { fields: { y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ value: "invalid_number" }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); if (result.options && result.options.series && result.options.series[0]) { expect(result.options.series[0].axisLine.lineStyle.color[0].valueFormatter).toBeDefined(); // Test error handling in valueFormatter const formatterResult = result.options.series[0].axisLine.lineStyle.color[0].valueFormatter(null); expect(typeof formatterResult).toBe("string"); } }); it("should handle area-stacked chart type", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "area-stacked", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 100, category: "A" }, { timestamp: "2023-01-01T01:00:00Z", value: 150, category: "A" }, { timestamp: "2023-01-01T00:00:00Z", value: 200, category: "B" }, { timestamp: "2023-01-01T01:00:00Z", value: 250, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); expect(result.options.series[0].areaStyle).toBeDefined(); }); it("should handle area chart type", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "area", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 100 }, { timestamp: "2023-01-01T01:00:00Z", value: 150 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series[0].areaStyle).toBeDefined(); }); it("should handle h-stacked chart type", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "h-stacked", }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], breakdown: [{ alias: "group" }], }, }, ], }; const searchData = [ [ { category: "A", value: 100, group: "Group1" }, { category: "B", value: 150, group: "Group1" }, { category: "A", value: 200, group: "Group2" }, { category: "B", value: 250, group: "Group2" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(0); }); it("should handle invalid timestamp processing in time series", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const metadata = { histogramInterval: "invalid_interval", dateTimeStartPoint: "invalid_date", timestampField: "timestamp", }; const searchData = [ [ { timestamp: "invalid_timestamp", value: 100 }, { timestamp: null, value: 200 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, metadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle complex tooltip scenarios for time series", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", show_tooltip: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value1" }, { alias: "value2" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value1: 100, value2: 200, category: "A" }, { timestamp: "2023-01-01T01:00:00Z", value1: null, value2: 150, category: "A" }, { timestamp: "2023-01-01T00:00:00Z", value1: 300, value2: null, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(typeof result.options.tooltip.formatter).toBe("function"); // Test complex tooltip formatting const mockParams = [ { value: ["2023-01-01T00:00:00Z", 100], seriesName: "A_value1" }, { value: ["2023-01-01T00:00:00Z", null], seriesName: "A_value2" }, ]; const tooltipResult = result.options.tooltip.formatter(mockParams); expect(typeof tooltipResult).toBe("string"); }); it("should handle maxYValue processing with decimal numbers", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", axis_max: "100.50", decimals: 2, }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "A", value: 95.25 }, { category: "B", value: 88.75 }, { category: "C", value: 92.33 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.yAxis).toBeDefined(); }); it("should handle null annotations list", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ timestamp: "2023-01-01T00:00:00Z", value: 100 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, null, // null annotations ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle empty data with axis borders", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", axis_border_show: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[]]; // Empty data const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle hovered series state changes", async () => { const hoveredState = { value: { setHoveredSeriesName: vi.fn(), }, }; const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ timestamp: "2023-01-01T00:00:00Z", value: 100 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, hoveredState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(typeof result.options.legend.formatter).toBe("function"); // Test hovered series functionality const legendResult = result.options.legend.formatter("test_series"); expect(legendResult).toBeDefined(); }); it("should handle scientific notation in axis labels", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", scientific_notation: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "large_value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", large_value: 1.23e15 }, { timestamp: "2023-01-01T01:00:00Z", large_value: 5.67e18 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(typeof result.options.yAxis.axisLabel.formatter).toBe("function"); }); it("should handle missing value configurations for different chart types", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", missing_value_handling: "interpolation", connect_nulls: false, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { timestamp: "2023-01-01T00:00:00Z", value: 100 }, { timestamp: "2023-01-01T01:00:00Z", value: null }, { timestamp: "2023-01-01T02:00:00Z", value: 200 }, { timestamp: "2023-01-01T03:00:00Z", value: undefined }, { timestamp: "2023-01-01T04:00:00Z", value: 150 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series[0].connectNulls).toBe(false); }); it("should handle extreme values in data processing", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "bar", }, queries: [ { fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [ [ { category: "A", value: Number.MAX_VALUE }, { category: "B", value: Number.MIN_VALUE }, { category: "C", value: Infinity }, { category: "D", value: -Infinity }, { category: "E", value: NaN }, { category: "F", value: 0 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series).toBeDefined(); }); it("should handle custom color palettes with insufficient colors", async () => { const schema = { ...mockPanelSchema, config: { ...mockPanelSchema.config, type: "line", color_palette: ["#FF0000", "#00FF00"], // Only 2 colors }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "series" }], }, }, ], }; const searchData = [[]]; // Create more series than available colors for (let i = 0; i < 5; i++) { searchData[0].push({ timestamp: "2023-01-01T00:00:00Z", value: Math.random() * 100, series: `Series_${i}`, }); } const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result).toBeDefined(); expect(result.options.series.length).toBe(5); }); }); }); describe("Show Gridlines Configuration", () => { it("should use default gridlines (true) when config is undefined", async () => { const schema = { ...mockPanelSchema, type: "line", config: {}, // No show_gridlines config }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 10 }, { timestamp: "2024-01-01T01:00:00Z", value: 20 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Check if the result has axis configuration with splitLine if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(true); } if (result.options?.yAxis?.splitLine) { expect(result.options.yAxis.splitLine.show).toBe(true); } // Ensure the result is valid expect(result.options).toBeDefined(); }); it("should respect explicit gridlines configuration (true)", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_gridlines: true }, }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 15 }, { timestamp: "2024-01-01T01:00:00Z", value: 25 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Check if the result has axis configuration with splitLine if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(true); } if (result.options?.yAxis?.splitLine) { expect(result.options.yAxis.splitLine.show).toBe(true); } expect(result.options).toBeDefined(); }); it("should respect explicit gridlines configuration (false)", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_gridlines: false }, }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 30 }, { timestamp: "2024-01-01T01:00:00Z", value: 40 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Check if the result has axis configuration with splitLine if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(false); } if (result.options?.yAxis?.splitLine) { expect(result.options.yAxis.splitLine.show).toBe(false); } expect(result.options).toBeDefined(); }); it("should apply gridlines to table charts", async () => { const schema = { ...mockPanelSchema, type: "table", config: { show_gridlines: true }, }; const searchData = [ [ { name: "Item1", count: 10, category: "A" }, { name: "Item2", count: 20, category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Table charts might not have traditional xAxis/yAxis expect(result.options).toBeDefined(); }); it("should apply gridlines to bar charts", async () => { const schema = { ...mockPanelSchema, type: "bar", config: { show_gridlines: false }, }; const searchData = [ [ { category: "A", value: 100 }, { category: "B", value: 200 }, { category: "C", value: 150 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Check if the result has axis configuration with splitLine if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(false); } if (result.options?.yAxis?.splitLine) { expect(result.options.yAxis.splitLine.show).toBe(false); } expect(result.options).toBeDefined(); }); it("should handle gridlines with null config value", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_gridlines: null }, }; const searchData = [[{ timestamp: "2024-01-01T00:00:00Z", value: 50 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // null is not undefined, so it uses the null value directly if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(null); } if (result.options?.yAxis?.splitLine) { expect(result.options.yAxis.splitLine.show).toBe(null); } expect(result.options).toBeDefined(); }); }); describe("Connect Nulls Configuration", () => { it("should use default connect_nulls (false) when config is undefined", async () => { const schema = { ...mockPanelSchema, type: "line", config: {}, // No connect_nulls config }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 10 }, { timestamp: "2024-01-01T01:00:00Z", value: null }, { timestamp: "2024-01-01T02:00:00Z", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); const lineSeries = result.options.series.find((s: any) => s.type === "line"); expect(lineSeries.connectNulls).toBe(false); }); it("should respect explicit connect_nulls configuration (true)", async () => { const schema = { ...mockPanelSchema, type: "line", config: { connect_nulls: true }, }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 15 }, { timestamp: "2024-01-01T01:00:00Z", value: null }, { timestamp: "2024-01-01T02:00:00Z", value: 35 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); const lineSeries = result.options.series.find((s: any) => s.type === "line"); expect(lineSeries.connectNulls).toBe(true); }); it("should respect explicit connect_nulls configuration (false)", async () => { const schema = { ...mockPanelSchema, type: "line", config: { connect_nulls: false }, }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 20 }, { timestamp: "2024-01-01T01:00:00Z", value: null }, { timestamp: "2024-01-01T02:00:00Z", value: 40 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); const lineSeries = result.options.series.find((s: any) => s.type === "line"); expect(lineSeries.connectNulls).toBe(false); }); it("should apply connect_nulls to area charts", async () => { const schema = { ...mockPanelSchema, type: "area", config: { connect_nulls: true }, }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 25 }, { timestamp: "2024-01-01T01:00:00Z", value: null }, { timestamp: "2024-01-01T02:00:00Z", value: 45 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); const areaSeries = result.options.series.find((s: any) => s.type === "line" && s.areaStyle); expect(areaSeries.connectNulls).toBe(true); }); it("should apply connect_nulls to area-stacked charts", async () => { const schema = { ...mockPanelSchema, type: "area-stacked", config: { connect_nulls: false }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "series" }], }, }, ], }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 30, series: "A" }, { timestamp: "2024-01-01T01:00:00Z", value: null, series: "A" }, { timestamp: "2024-01-01T02:00:00Z", value: 50, series: "A" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); const areaSeries = result.options.series.find((s: any) => s.stack === "Total"); expect(areaSeries.connectNulls).toBe(false); }); it("should not apply connect_nulls to bar charts", async () => { const schema = { ...mockPanelSchema, type: "bar", config: { connect_nulls: true }, // Should be ignored for bar charts }; const searchData = [ [ { category: "A", value: 10 }, { category: "B", value: 20 }, { category: "C", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Bar charts don't use connectNulls property const barSeries = result.options.series.find((s: any) => s.type === "bar"); if (barSeries) { expect(barSeries.connectNulls).toBeUndefined(); } else { // Test passes if no bar series found (different chart structure) expect(result.options.series).toBeDefined(); } }); }); describe("Enhanced Legend Calculations Integration", () => { it("should calculate right legend width automatically", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_legends: true, legends_position: "right", legends_type: "plain", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "service" }], }, }, ], }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 10, service: "web-server" }, { timestamp: "2024-01-01T01:00:00Z", value: 20, service: "database" }, { timestamp: "2024-01-01T02:00:00Z", value: 30, service: "cache-server" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should use calculateRightLegendWidth (mock returns 140) expect(result.options.grid.right).toBeGreaterThan(0); expect(result.options.legend.textStyle.width).toBeGreaterThan(0); // 160 - 55 expect(result.options.legend.left).toBeGreaterThanOrEqual(0); }); it("should calculate right legend width with scroll type", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_legends: true, legends_position: "right", legends_type: "scroll", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "series" }], }, }, ], }; const searchData = [ [ ...Array.from({ length: 20 }, (_, i) => ({ timestamp: `2024-01-01T${String(i).padStart(2, "0")}:00:00Z`, value: i * 5, series: `Series_${i}`, })), ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should use calculateRightLegendWidth with scrollable=true expect(result.options.grid.right).toBeGreaterThan(0); expect(result.options.legend.textStyle.width).toBeGreaterThan(0); }); it("should calculate bottom legend height for plain legends", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_legends: true, legends_type: "plain", legends_position: "bottom", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "service" }], }, }, ], }; const searchData = [ [ ...Array.from({ length: 8 }, (_, i) => ({ timestamp: `2024-01-01T${String(i).padStart(2, "0")}:00:00Z`, value: i * 10, service: `Service_${i}`, })), ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should use calculateBottomLegendHeight (mock modifies legend and grid) expect(result.options.legend.top).toBeGreaterThan(0); // 400 - 90 = 310 expect(result.options.legend.height).toBeGreaterThan(0); expect(result.options.grid.bottom).toBeGreaterThan(0); }); it("should calculate bottom legend height with auto position (null)", async () => { const schema = { ...mockPanelSchema, type: "area", config: { show_legends: true, legends_type: "plain", legends_position: null, // Auto position (treated as bottom) }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "category" }], }, }, ], }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 40, category: "Alpha" }, { timestamp: "2024-01-01T01:00:00Z", value: 50, category: "Beta" }, { timestamp: "2024-01-01T02:00:00Z", value: 60, category: "Gamma" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Auto position should trigger bottom legend calculation expect(result.options.legend.top).toBeGreaterThan(0); expect(result.options.legend.height).toBeGreaterThan(0); expect(result.options.grid.bottom).toBeGreaterThan(0); }); it("should not apply bottom legend calculation for scroll type", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_legends: true, legends_type: "scroll", legends_position: "bottom", }, }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 20 }, { timestamp: "2024-01-01T01:00:00Z", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should not apply calculateBottomLegendHeight for scroll type expect(result.options.legend.left).toBe("0"); expect(result.options.legend.top).toBe("bottom"); }); it("should handle explicit legend width configuration", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_legends: true, legends_position: "right", legend_width: { value: 200, unit: "px" }, }, }; const searchData = [[{ timestamp: "2024-01-01T00:00:00Z", value: 25 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should use explicit width instead of calculating expect(result.options.grid.right).toBe(200); expect(result.options.legend.textStyle.width).toBeGreaterThan(0); // 200 - 55 }); it("should handle legend width as percentage", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_legends: true, legends_position: "right", legend_width: { value: 25, unit: "%" }, }, }; const searchData = [[{ timestamp: "2024-01-01T00:00:00Z", value: 35 }]]; mockChartPanelRef.value.offsetWidth = 800; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should calculate 25% of 800 = 200 expect(result.options.grid.right).toBe(200); expect(result.options.legend.textStyle.width).toBeGreaterThan(0); // 200 - 55 }); }); describe("Comprehensive Integration Tests for New SQL Features", () => { it("should handle combination of all new features together", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_gridlines: false, connect_nulls: true, show_legends: true, legends_position: "right", legends_type: "plain", axis_width: 20, axis_border_show: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [ { alias: "cpu_usage", label: "CPU %" }, { alias: "memory_usage", label: "Memory %" }, ], breakdown: [{ alias: "server" }], }, }, ], }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", cpu_usage: 50, memory_usage: 70, server: "web-1" }, { timestamp: "2024-01-01T01:00:00Z", cpu_usage: null, memory_usage: 75, server: "web-1" }, { timestamp: "2024-01-01T02:00:00Z", cpu_usage: 60, memory_usage: null, server: "web-1" }, { timestamp: "2024-01-01T00:00:00Z", cpu_usage: 40, memory_usage: 60, server: "web-2" }, { timestamp: "2024-01-01T01:00:00Z", cpu_usage: 45, memory_usage: 65, server: "web-2" }, { timestamp: "2024-01-01T02:00:00Z", cpu_usage: 50, memory_usage: 70, server: "web-2" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Test gridlines are disabled // Check if the result has axis configuration with splitLine if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(false); } if (result.options?.yAxis?.splitLine) { expect(result.options.yAxis.splitLine.show).toBe(false); } expect(result.options).toBeDefined(); // Test axis borders are enabled if (result.options?.xAxis?.axisLine) { expect(result.options.xAxis.axisLine.show).toBe(true); } if (result.options?.yAxis?.axisLine) { expect(result.options.yAxis.axisLine.show).toBe(true); } // Test connect nulls is enabled for line series const cpuSeries = result.options.series.find((s: any) => s.name && s.name.includes("CPU")); if (cpuSeries) { expect(cpuSeries.connectNulls).toBe(true); } // Test right legend positioning expect(result.options.legend.orient).toBe("vertical"); expect(result.options.grid.right).toBeGreaterThan(0); // Grid left might have different default value based on axis configuration expect(result.options.grid.left).toBeGreaterThanOrEqual(5); }); it("should handle large dataset with legend calculations", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_gridlines: true, connect_nulls: false, show_legends: true, legends_position: "bottom", legends_type: "plain", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "metric_name" }], }, }, ], }; // Create large dataset with many different series const searchData = [[]]; for (let i = 0; i < 50; i++) { for (let j = 0; j < 24; j++) { searchData[0].push({ timestamp: `2024-01-01T${String(j).padStart(2, "0")}:00:00Z`, value: Math.random() * 100, metric_name: `Metric_${i}`, }); } } const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should handle large dataset and apply bottom legend calculations expect(result.options.legend.top).toBeGreaterThan(0); // 400 - 90 expect(result.options.legend.height).toBeGreaterThan(0); expect(result.options.grid.bottom).toBeGreaterThan(0); expect(result.options.series.length).toBeGreaterThan(10); }); it("should handle metric charts with new configurations", async () => { const schema = { ...mockPanelSchema, type: "metric", config: { show_gridlines: false, background: { value: { color: "#1E88E5" } }, }, }; const searchData = [[{ value: 85.5 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); expect(result.options.backgroundColor).toBe("#1E88E5"); // Metric charts should handle gridlines configuration expect(result.extras.isTimeSeries).toBe(false); }); it("should handle gauge charts with new features", async () => { const schema = { ...mockPanelSchema, type: "gauge", config: { show_gridlines: true, }, queries: [ { config: { min: 0, max: 100 }, fields: { x: [{ alias: "category" }], y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ category: "CPU", value: 75 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Gauge charts have specialized structure - just verify basic functionality expect(result).toBeDefined(); expect(result.options).toBeDefined(); expect(result.extras.isTimeSeries).toBe(false); }); it("should handle pie charts with legend positioning", async () => { const schema = { ...mockPanelSchema, type: "pie", config: { show_gridlines: false, // Not applicable to pie charts show_legends: true, legends_position: "right", }, }; const searchData = [ [ { category: "Desktop", value: 60 }, { category: "Mobile", value: 30 }, { category: "Tablet", value: 10 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Pie charts should handle legend positioning expect(result.options.legend.orient).toBe("vertical"); expect(result.extras.isTimeSeries).toBe(false); }); it("should handle stacked charts with connect_nulls", async () => { const schema = { ...mockPanelSchema, type: "stacked", config: { connect_nulls: true, show_gridlines: true, }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: [{ alias: "stack_category" }], }, }, ], }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 20, stack_category: "A" }, { timestamp: "2024-01-01T01:00:00Z", value: null, stack_category: "A" }, { timestamp: "2024-01-01T02:00:00Z", value: 40, stack_category: "A" }, { timestamp: "2024-01-01T00:00:00Z", value: 30, stack_category: "B" }, { timestamp: "2024-01-01T01:00:00Z", value: 35, stack_category: "B" }, { timestamp: "2024-01-01T02:00:00Z", value: 45, stack_category: "B" }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should apply connect_nulls to stacked series const stackedSeries = result.options.series.find((s: any) => s.stack); if (stackedSeries) { expect(stackedSeries.connectNulls).toBe(true); } // Check if the result has axis configuration with splitLine if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(true); } if (result.options?.yAxis?.splitLine) { expect(result.options.yAxis.splitLine.show).toBe(true); } expect(result.options).toBeDefined(); }); it("should handle heatmap charts with gridlines", async () => { const schema = { ...mockPanelSchema, type: "heatmap", config: { show_gridlines: false, }, queries: [ { fields: { x: [{ alias: "hour" }], y: [{ alias: "day" }], z: [{ alias: "temperature" }], }, }, ], }; const searchData = [ [ { hour: 0, day: "Monday", temperature: 20 }, { hour: 1, day: "Monday", temperature: 18 }, { hour: 0, day: "Tuesday", temperature: 22 }, { hour: 1, day: "Tuesday", temperature: 24 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Check if the result has axis configuration with splitLine if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(false); } if (result.options?.yAxis?.splitLine) { expect(result.options.yAxis.splitLine.show).toBe(false); } expect(result.options).toBeDefined(); expect(result.extras.isTimeSeries).toBe(false); }); it("should handle edge cases with invalid legend width", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_legends: true, legends_position: "right", legend_width: { value: "invalid", unit: "px" }, }, }; const searchData = [[{ timestamp: "2024-01-01T00:00:00Z", value: 45 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should fall back to automatic calculation when width is invalid expect(result.options.grid.right).toBeGreaterThan(0); // Mock calculateRightLegendWidth returns 160 expect(result.options.legend.textStyle.width).toBeGreaterThan(0); }); it("should handle zero chart panel dimensions", async () => { const zeroChartPanelRef = { value: { offsetWidth: 0, offsetHeight: 0, }, }; const schema = { ...mockPanelSchema, type: "line", config: { show_legends: true, legends_position: "right", }, }; const searchData = [[{ timestamp: "2024-01-01T00:00:00Z", value: 55 }]]; const result = await convertSQLData( schema, searchData, mockStore, zeroChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should handle zero dimensions gracefully expect(result.options.grid.right).toBeGreaterThanOrEqual(0); expect(result.options.legend.textStyle.width).toBeGreaterThanOrEqual(60); }); it("should validate that new features work with all chart types", async () => { const chartTypes = ["line", "area", "bar", "scatter", "area-stacked", "stacked"]; for (const chartType of chartTypes) { const schema = { ...mockPanelSchema, type: chartType, config: { show_gridlines: true, connect_nulls: ["line", "area", "area-stacked"].includes(chartType), }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: [{ alias: "value" }], breakdown: chartType.includes("stacked") ? [{ alias: "category" }] : [], }, }, ], }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 10, category: chartType.includes("stacked") ? "A" : undefined, }, { timestamp: "2024-01-01T01:00:00Z", value: null, category: chartType.includes("stacked") ? "A" : undefined, }, { timestamp: "2024-01-01T02:00:00Z", value: 30, category: chartType.includes("stacked") ? "A" : undefined, }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // All chart types should respect gridlines (if they have axes) if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(true); } if (result.options?.yAxis?.splitLine) { expect(result.options.yAxis.splitLine.show).toBe(true); } expect(result.options.series.length).toBeGreaterThan(0); } }); it("should handle time series detection with new configurations", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_gridlines: true, connect_nulls: true, }, }; const searchData = [ [ { timestamp: "2024-01-01T00:00:00Z", value: 10 }, { timestamp: "2024-01-01T01:00:00Z", value: 20 }, { timestamp: "2024-01-01T02:00:00Z", value: 30 }, ], ]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Test that the function handles time series configuration expect(result.extras).toBeDefined(); if (result.options?.xAxis?.splitLine) { expect(result.options.xAxis.splitLine.show).toBe(true); } }); it("should handle empty data with new configuration options", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_gridlines: false, connect_nulls: true, show_legends: true, }, }; const searchData = [[]]; // Empty data const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should handle empty data gracefully - might return options object for progress indication expect(result).toBeDefined(); expect(result.options).toBeDefined(); }); it("should handle invalid data structure with new configurations", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_gridlines: true, connect_nulls: false, }, queries: [ { fields: { x: null, // Invalid field configuration y: [{ alias: "value" }], }, }, ], }; const searchData = [[{ timestamp: "2024-01-01T00:00:00Z", value: 100 }]]; const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should return null for invalid configuration expect(result.options).toBeNull(); }); it("should handle performance with extreme legend configurations", async () => { const schema = { ...mockPanelSchema, type: "line", config: { show_gridlines: true, connect_nulls: true, show_legends: true, legends_position: "bottom", legends_type: "plain", }, queries: [ { fields: { x: [{ alias: "timestamp" }], y: Array.from({ length: 10 }, (_, i) => ({ alias: `metric_${i}` })), // 10 Y fields breakdown: [{ alias: "server" }], }, }, ], }; // Create data with multiple servers and metrics const searchData = [[]]; const servers = ["web-1", "web-2", "web-3", "db-1", "db-2"]; for (const server of servers) { for (let hour = 0; hour < 24; hour++) { const dataPoint: any = { timestamp: `2024-01-01T${String(hour).padStart(2, "0")}:00:00Z`, server, }; // Add multiple metric values for (let i = 0; i < 10; i++) { dataPoint[`metric_${i}`] = Math.random() * 100; } searchData[0].push(dataPoint); } } const result = await convertSQLData( schema, searchData, mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); // Should handle complex configuration without errors expect(result.options).toBeDefined(); expect(result.options.series.length).toBeGreaterThan(10); expect(result.options.legend.top).toBeGreaterThan(0); // Bottom legend calculation applied expect(result.options.grid.bottom).toBeGreaterThan(0); }); }); // A conditional overlay series (`CASE WHEN ... THEN value END`) is NULL on // every healthy bucket. Filtering rows on ALL y keys being non-null deleted // those rows outright, taking the base series' value and the x category with // them — the anomaly detection charts lost their whole baseline. describe("sparse y column does not delete the row", () => { const sparsePanel = () => ({ queries: [ { fields: { x: [{ alias: "zo_sql_key" }], y: [{ alias: "zo_sql_num" }, { alias: "anomaly_value" }], z: [], breakdown: [], }, }, ], config: { top_results: 10, top_results_others: false, connect_nulls: false }, type: "line", }); // Ten buckets, three of them flagged; the overlay column is null on the rest. const rows = [ { zo_sql_key: "16:25", zo_sql_num: 1900, anomaly_value: null }, { zo_sql_key: "16:30", zo_sql_num: 2000, anomaly_value: null }, { zo_sql_key: "16:35", zo_sql_num: 2050, anomaly_value: null }, { zo_sql_key: "16:40", zo_sql_num: 2140, anomaly_value: null }, { zo_sql_key: "16:45", zo_sql_num: 3000, anomaly_value: 3000 }, { zo_sql_key: "16:50", zo_sql_num: 3000, anomaly_value: 3000 }, { zo_sql_key: "16:55", zo_sql_num: 2100, anomaly_value: null }, { zo_sql_key: "17:00", zo_sql_num: 2980, anomaly_value: 2980 }, { zo_sql_key: "17:05", zo_sql_num: 2000, anomaly_value: null }, { zo_sql_key: "17:10", zo_sql_num: 1950, anomaly_value: null }, ]; const render = async (panel: any, data: any[]) => await convertSQLData( panel, [data], mockStore, mockChartPanelRef, mockHoveredSeriesState, mockResultMetaData, mockMetadata, mockChartPanelStyle, mockAnnotations, ); it("keeps every bucket on the x axis", async () => { const result = await render(sparsePanel(), rows); expect(result.options.xAxis[0].data).toHaveLength(10); }); it("keeps the dense series complete", async () => { const result = await render(sparsePanel(), rows); const base = result.options.series.find((s: any) => s.data.includes(1900)); expect(base.data).toEqual([1900, 2000, 2050, 2140, 3000, 3000, 2100, 2980, 2000, 1950]); }); // The gap is what breaks the overlay line at a bucket that is NOT flagged. it("holds the sparse series null where it has no value", async () => { const result = await render(sparsePanel(), rows); const overlay = result.options.series.find((s: any) => !s.data.includes(1900)); expect(overlay.data).toHaveLength(10); expect(overlay.data[6]).toBeNull(); expect(overlay.data[4]).toBe(3000); }); // Unchanged behaviour: a row carrying no y value at all is still dropped. it("still drops a row where every y column is null", async () => { const withEmptyRow = [ ...rows.slice(0, 2), { zo_sql_key: "16:33", zo_sql_num: null, anomaly_value: null }, ...rows.slice(2), ]; const result = await render(sparsePanel(), withEmptyRow); expect(result.options.xAxis[0].data).toHaveLength(10); expect(result.options.xAxis[0].data).not.toContain("16:33"); }); // A single-y panel takes the same path; `some` and `every` agree there. it("leaves a single-y panel unchanged", async () => { const panel = sparsePanel(); panel.queries[0].fields.y = [{ alias: "zo_sql_num" }]; const result = await render(panel, rows); expect(result.options.xAxis[0].data).toHaveLength(10); }); }); }); describe("multi-query heatmap pinned colour range", () => { const store = { state: { theme: "light", zoConfig: { max_dashboard_series: 100 } } }; const query = () => ({ customQuery: true, fields: { x: [{ alias: "x" }], y: [{ alias: "y" }], z: [{ alias: "z" }], breakdown: [], }, }); const searchData = [ [ { x: "a", y: "r", z: 5 }, { x: "b", y: "r", z: 20 }, ], [ { x: "a", y: "r", z: 40 }, { x: "b", y: "r", z: 80 }, ], ]; const render = (config: Record) => { vi.mocked(calculateGridPositions).mockReturnValueOnce({ gridArray: [ { left: "0%", top: "0%", width: "50%", height: "100%" }, { left: "50%", top: "0%", width: "50%", height: "100%" }, ], gridWidth: 400, gridHeight: 400, gridNoOfRow: 1, gridNoOfCol: 2, } as any); return convertMultiSQLData( { type: "heatmap", config: { decimals: 2, ...config }, queries: [query(), query()] }, searchData, store, { value: { offsetWidth: 800, offsetHeight: 400 } }, {}, { value: [[{}], [{}]] }, { queries: [{}, {}] }, {}, [], ); }; it("keeps the shared automatic range when nothing is pinned", async () => { const { visualMap } = (await render({})).options; expect([visualMap.min, visualMap.max]).toEqual([0, 80]); }); it("replaces the shared range with both pins", async () => { const { visualMap } = (await render({ heatmap_color_min: 10, heatmap_color_max: 30 })).options; expect([visualMap.min, visualMap.max]).toEqual([10, 30]); }); it("pins only the max over the shared automatic min", async () => { const { visualMap } = (await render({ heatmap_color_max: 30 })).options; expect([visualMap.min, visualMap.max]).toEqual([0, 30]); }); it("keeps a min pin that one query alone would reject", async () => { // 50 is above query 0's own max of 20 but below the shared max of 80. const { visualMap } = (await render({ heatmap_color_min: 50 })).options; expect([visualMap.min, visualMap.max]).toEqual([50, 80]); }); it("ignores an inverted pair and uses the shared automatic range", async () => { const { visualMap } = (await render({ heatmap_color_min: 30, heatmap_color_max: 10 })).options; expect([visualMap.min, visualMap.max]).toEqual([0, 80]); }); it("keeps real values in every query's cells", async () => { const { series } = (await render({ heatmap_color_max: 30 })).options; expect(series[1].data.map((cell: any) => cell[2])).toEqual([40, 80]); }); it("colours every tile from one palette and scale over the shared range", async () => { const { visualMap } = (await render({ heatmap_palette: "viridis", heatmap_color_scale: "log" })) .options; // The log floor is the smallest positive cell of any query: query 0's 5. expect([visualMap.min, visualMap.max]).toEqual([5, 80]); expect(visualMap.inRange.color[0]).toBe("#440154"); expect(visualMap.inRange.color.at(-1)).toBe("#fde725"); }); }); describe("multi-query heatmap tiles", () => { const store = { state: { theme: "light", zoConfig: { max_dashboard_series: 100 } } }; const query = () => ({ customQuery: true, fields: { x: [{ alias: "x" }], y: [{ alias: "y" }], z: [{ alias: "z" }], breakdown: [], }, }); const render = (searchData: any[]) => { vi.mocked(calculateGridPositions).mockReturnValueOnce({ gridArray: [ { left: "0%", top: "0%", width: "50%", height: "100%" }, { left: "50%", top: "0%", width: "50%", height: "100%" }, ], gridWidth: 400, gridHeight: 400, gridNoOfRow: 1, gridNoOfCol: 2, } as any); return convertMultiSQLData( { type: "heatmap", config: { decimals: 2 }, queries: [query(), query()] }, searchData, store, { value: { offsetWidth: 800, offsetHeight: 400 } }, {}, { value: [[{}], [{}]] }, { queries: [{}, {}] }, {}, [], ); }; it("colours all-negative tiles over their negative range", async () => { const { visualMap } = ( await render([ [ { x: "a", y: "r", z: -5 }, { x: "b", y: "r", z: -20 }, ], [ { x: "a", y: "r", z: -40 }, { x: "b", y: "r", z: -2 }, ], ]) ).options; expect([visualMap.min, visualMap.max]).toEqual([-40, -2]); }); it("hands drilldowns each tile's raw row values by its y axis", async () => { const result: any = await render([[{ x: "a", y: "", z: 1 }], [{ x: "a", y: "r2", z: 1 }]]); expect(result.extras.heatmapRowValues).toEqual([[""], ["r2"]]); }); it("surfaces a later tile's duplicate-row warning on the panel", async () => { const result: any = await render([ [{ x: "a", y: "r", z: 1 }], [ { x: "a", y: "r", z: 1 }, { x: "a", y: "r", z: 2 }, ], ]); expect(result.extras.limitNumberOfSeriesWarningMessage).toBe( "Duplicate (x, y) rows found: 1; each affected cell shows the first row's value", ); }); }); describe("heatmap drag-to-zoom and cursor sync", () => { const store = { state: { theme: "light", timezone: "UTC", zoConfig: { max_dashboard_series: 100, timestamp_column: "_timestamp" }, }, }; const builderQuery = () => ({ customQuery: false, fields: { x: [{ alias: "x", functionName: "histogram", args: [{ value: { field: "_timestamp" } }] }], y: [{ alias: "y" }], z: [{ alias: "z" }], breakdown: [], }, }); const customQuery = () => ({ ...builderQuery(), customQuery: true }); const rows = [ { x: "2024-01-15T10:00:00", y: "r", z: 1 }, { x: "2024-01-15T10:01:00", y: "r", z: 2 }, ]; const range = { startTime: Date.parse("2024-01-15T10:00:00Z") * 1000, endTime: Date.parse("2024-01-15T10:02:00Z") * 1000, }; const render = (query: any, resultMetaData: any = [{ histogram_interval: 60 }]) => convertSQLData( { type: "heatmap", config: {}, queries: [query] }, [rows], store, { value: { offsetWidth: 800, offsetHeight: 400 } }, { value: null }, resultMetaData, { queries: [range] }, {}, [], ); it("zooms and syncs a builder heatmap whose x is histogram(_timestamp)", async () => { const result: any = await render(builderQuery()); expect(result.options.toolbox.show).toBe(true); expect(result.extras.isTimeSeries).toBe(true); // The bucket starting at the range end is drawn too, as the filled x axis has always done. expect(result.extras.heatmapTimeAxis.columns).toHaveLength(3); }); it("keeps a heatmap with a categorical x without zoom or sync", async () => { const result: any = await render(customQuery()); expect(result.options.toolbox.show).toBe(false); expect(result.extras.isTimeSeries).toBe(false); expect(result.extras.heatmapTimeAxis).toBeUndefined(); }); it("keeps a builder histogram heatmap without a known interval without zoom", async () => { const result: any = await render(builderQuery(), [{}]); expect(result.options.toolbox.show).toBe(false); expect(result.extras.heatmapTimeAxis).toBeUndefined(); }); it("drops zoom and sync from heatmap tiles whose columns differ", async () => { vi.mocked(calculateGridPositions).mockReturnValueOnce({ gridArray: [ { left: "0%", top: "0%", width: "50%", height: "100%" }, { left: "50%", top: "0%", width: "50%", height: "100%" }, ], gridWidth: 400, gridHeight: 400, gridNoOfRow: 1, gridNoOfCol: 2, } as any); const result: any = await convertMultiSQLData( { type: "heatmap", config: {}, queries: [builderQuery(), builderQuery()] }, [rows, rows], store, { value: { offsetWidth: 800, offsetHeight: 400 } }, { value: null }, { value: [[{ histogram_interval: 60 }], [{ histogram_interval: 30 }]] }, { queries: [range, range] }, {}, [], ); expect(result.extras.heatmapTimeAxis).toBeUndefined(); expect(result.extras.isTimeSeries).toBe(false); expect(result.options.toolbox.show).toBe(false); }); }); describe("multi-query heatmap tiles share one cell budget", () => { const store = { state: { theme: "light", zoConfig: { max_dashboard_series: 100 } } }; const query = () => ({ customQuery: true, fields: { x: [{ alias: "x" }], y: [{ alias: "y" }], z: [{ alias: "z" }], breakdown: [] }, }); const grid = () => vi.mocked(calculateGridPositions).mockReturnValueOnce({ gridArray: [ { left: "0%", top: "0%", width: "50%", height: "100%" }, { left: "50%", top: "0%", width: "50%", height: "100%" }, ], gridWidth: 400, gridHeight: 400, gridNoOfRow: 1, gridNoOfCol: 2, } as any); const render = (searchData: any[]) => { grid(); return convertMultiSQLData( { type: "heatmap", config: { decimals: 2 }, queries: [query(), query()] }, searchData, store, { value: { offsetWidth: 800, offsetHeight: 400 } }, {}, { value: [[{}], [{}]] }, { queries: [{}, {}] }, {}, [], ); }; it("cuts each tile to half the budget, so the panel stays within it", async () => { const tile = Array.from({ length: 300 }, (_u, row) => Array.from({ length: 100 }, (_v, col) => ({ x: `c${col}`, y: `r${row}`, z: 1 })), ).flat(); const result: any = await render([tile, tile]); const cells = result.options.series.map((series: any) => series.data.length); expect(cells.every((count: number) => count <= 25_000)).toBe(true); expect(cells.reduce((sum: number, count: number) => sum + count, 0)).toBeLessThanOrEqual( 50_000, ); }); it("labels a hovered cell from its own tile's y axis when an earlier tile is empty", async () => { const result: any = await render([[], [{ x: "a", y: "row-b", z: 5 }]]); expect(result.options.series).toHaveLength(1); const text = result.options.tooltip.formatter({ seriesIndex: 0, value: [0, 0, 5], name: "a", marker: "", }); expect(text).toContain("row-b"); }); });