mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 08:09:27 +00:00
10934 lines
298 KiB
TypeScript
10934 lines
298 KiB
TypeScript
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
import { convertMultiSQLData, convertSQLData } from "@/utils/dashboard/convertSQLData";
|
|
|
|
// 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("");
|
|
});
|
|
|
|
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("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);
|
|
});
|
|
});
|
|
});
|