mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 16:09:05 +00:00
304 lines
8.5 KiB
TypeScript
304 lines
8.5 KiB
TypeScript
// Copyright 2023 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 {
|
|
applyAutoSQLTimeSeries,
|
|
applyCustomSQLTimeSeries,
|
|
} from "../sqlTimeSeriesConverter";
|
|
import { isGivenFieldInOrderBy } from "../../query/sqlUtils";
|
|
import { applyLegendConfiguration } from "../legendConfiguration";
|
|
import { applySeriesColorMappings } from "../chartColorUtils";
|
|
|
|
import { buildSQLContext } from "./shared/contextBuilder";
|
|
import { applyLineAreaScatterBarChart } from "./charts/convertSQLLineAreaChart";
|
|
import { applyHBarChart } from "./charts/convertSQLHBarChart";
|
|
import { applyStackedChart } from "./charts/convertSQLStackedChart";
|
|
import { applyHStackedChart } from "./charts/convertSQLHStackedChart";
|
|
import { applyPieDonutChart } from "./charts/convertSQLPieDonutChart";
|
|
import { applyHeatmapChart } from "./charts/convertSQLHeatmapChart";
|
|
import { applyMetricChart } from "./charts/convertSQLMetricChart";
|
|
import { applyGaugeChart } from "./charts/convertSQLGaugeChart";
|
|
|
|
/**
|
|
* Converts SQL data into a format suitable for rendering a chart.
|
|
* This is the main orchestrator: builds shared context, dispatches to a
|
|
* chart-type handler, then applies common post-processing.
|
|
*/
|
|
export const convertSQLChartData = async (
|
|
panelSchema: any,
|
|
searchQueryData: any,
|
|
store: any,
|
|
chartPanelRef: any,
|
|
hoveredSeriesState: any,
|
|
resultMetaData: any,
|
|
metadata: any,
|
|
chartPanelStyle: any,
|
|
annotations: any,
|
|
) => {
|
|
// Build the full shared context (all pre-switch logic lives here)
|
|
const ctx = buildSQLContext(
|
|
panelSchema,
|
|
searchQueryData,
|
|
store,
|
|
chartPanelRef,
|
|
hoveredSeriesState,
|
|
resultMetaData,
|
|
metadata,
|
|
chartPanelStyle,
|
|
annotations,
|
|
);
|
|
|
|
// Guard returned from contextBuilder (invalid/empty input)
|
|
if (!ctx) {
|
|
return { options: null };
|
|
}
|
|
|
|
const {
|
|
options,
|
|
extras,
|
|
yAxisKeys,
|
|
convertedTimeStampToDataFormat,
|
|
getAnnotationMarkLine,
|
|
getSeriesMarkArea,
|
|
} = ctx;
|
|
|
|
// Now set the series values as per the chart data
|
|
// Override any configs if required as per the chart type
|
|
switch (panelSchema.type) {
|
|
case "area-stacked":
|
|
case "line":
|
|
case "area":
|
|
case "scatter":
|
|
case "bar": {
|
|
applyLineAreaScatterBarChart(ctx);
|
|
break;
|
|
}
|
|
case "h-bar": {
|
|
applyHBarChart(ctx);
|
|
break;
|
|
}
|
|
case "pie": {
|
|
applyPieDonutChart(ctx);
|
|
break;
|
|
}
|
|
case "donut": {
|
|
applyPieDonutChart(ctx);
|
|
break;
|
|
}
|
|
case "stacked": {
|
|
applyStackedChart(ctx);
|
|
break;
|
|
}
|
|
case "heatmap": {
|
|
applyHeatmapChart(ctx);
|
|
break;
|
|
}
|
|
case "h-stacked": {
|
|
applyHStackedChart(ctx);
|
|
break;
|
|
}
|
|
case "metric": {
|
|
applyMetricChart(ctx);
|
|
break;
|
|
}
|
|
case "gauge": {
|
|
applyGaugeChart(ctx);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// POST-PROCESSING (verbatim from original convertSQLData)
|
|
|
|
let isTimeSeriesFlag = false;
|
|
|
|
// auto SQL: if x axis has time series
|
|
if (
|
|
applyAutoSQLTimeSeries(
|
|
options,
|
|
panelSchema,
|
|
store,
|
|
metadata,
|
|
hoveredSeriesState,
|
|
)
|
|
) {
|
|
// set timeseries flag as a true
|
|
isTimeSeriesFlag = true;
|
|
}
|
|
|
|
//custom SQL: check if it is timeseries or not
|
|
if (
|
|
applyCustomSQLTimeSeries(
|
|
options,
|
|
panelSchema,
|
|
store,
|
|
metadata,
|
|
hoveredSeriesState,
|
|
)
|
|
) {
|
|
// set timeseries flag as a true
|
|
isTimeSeriesFlag = true;
|
|
}
|
|
|
|
// stacked chart sort by y axis
|
|
// ignore if time series chart
|
|
if (
|
|
["stacked", "h-stacked", "area-stacked"].includes(panelSchema?.type) &&
|
|
isTimeSeriesFlag == false
|
|
) {
|
|
// get x axis object
|
|
// for h-stacked, categorical axis is y axis
|
|
// for stacked and area-stacked, categorical axis is x axis
|
|
const xAxisObj =
|
|
panelSchema.type == "h-stacked" ? options.yAxis : options.xAxis;
|
|
|
|
// check if order by uses y axis field
|
|
// will return null if not exist
|
|
// will return ASC or DESC if exist
|
|
const isYAxisExistInOrderBy = await isGivenFieldInOrderBy(
|
|
metadata?.queries[0]?.query ?? "",
|
|
yAxisKeys[0],
|
|
);
|
|
if (isYAxisExistInOrderBy) {
|
|
// Calculate the total for each series and combine it with the corresponding x-axis label
|
|
const totals = new Map();
|
|
for (let i = 0; i < xAxisObj[0]?.data?.length; i++) {
|
|
const total = options?.series?.reduce(
|
|
(sum: number, currentSeries: any) =>
|
|
sum + (currentSeries?.data[i] ?? 0),
|
|
0,
|
|
);
|
|
totals.set(i, { label: xAxisObj[0]?.data[i], total });
|
|
}
|
|
|
|
// Sort the indices by total in the specified order
|
|
// ASC for ascending, DESC for descending
|
|
const sortedIndices = Array.from(totals.keys()).sort((a, b) => {
|
|
const diff = totals.get(a).total - totals.get(b).total;
|
|
return isYAxisExistInOrderBy == "ASC" ? diff : -diff;
|
|
});
|
|
|
|
// Create new sorted arrays for the x-axis labels and series
|
|
xAxisObj[0].data = sortedIndices.map((i) => totals.get(i).label);
|
|
options.series = options?.series?.map((currentSeries: any) => {
|
|
currentSeries.data = sortedIndices?.map((i) => currentSeries?.data[i]);
|
|
return currentSeries;
|
|
});
|
|
}
|
|
}
|
|
|
|
// Apply all legend configurations using the new centralized function
|
|
applyLegendConfiguration(
|
|
panelSchema,
|
|
chartPanelRef,
|
|
hoveredSeriesState,
|
|
options,
|
|
);
|
|
|
|
//check if is there any data else filter out axis or series data
|
|
// for metric, gauge we does not have data field
|
|
if (!["metric", "gauge"].includes(panelSchema.type)) {
|
|
options.series = options.series.filter((it: any) => it.data?.length);
|
|
if (panelSchema.type == "h-bar" || panelSchema.type == "h-stacked") {
|
|
options.xAxis = options.xAxis;
|
|
} else if (!["pie", "donut"].includes(panelSchema.type)) {
|
|
options.yAxis = options.yAxis;
|
|
}
|
|
}
|
|
// allowed to zoom, only if timeseries
|
|
options.toolbox.show = options.toolbox.show && isTimeSeriesFlag;
|
|
|
|
if (
|
|
[
|
|
"area",
|
|
"area-stacked",
|
|
"bar",
|
|
"h-bar",
|
|
"line",
|
|
"scatter",
|
|
"stacked",
|
|
"h-stacked",
|
|
].includes(panelSchema.type) &&
|
|
isTimeSeriesFlag &&
|
|
!panelSchema.config.trellis?.layout
|
|
) {
|
|
options.series.push({
|
|
type: "line",
|
|
data: [[convertedTimeStampToDataFormat, null]],
|
|
markLine: getAnnotationMarkLine(),
|
|
markArea: getSeriesMarkArea(),
|
|
zlevel: 1,
|
|
});
|
|
}
|
|
|
|
// Apply series color mappings via reusable helper
|
|
applySeriesColorMappings(
|
|
options.series,
|
|
panelSchema?.config?.color?.colorBySeries,
|
|
store.state.theme,
|
|
);
|
|
|
|
// Apply label truncation to x-axis only
|
|
if (panelSchema.config?.axis_label_truncate_width) {
|
|
if (Array.isArray(options.xAxis)) {
|
|
options.xAxis.forEach((axis: any) => {
|
|
if (!axis.axisLabel) axis.axisLabel = {};
|
|
axis.axisLabel.width = panelSchema.config.axis_label_truncate_width;
|
|
axis.axisLabel.overflow = "truncate";
|
|
});
|
|
} else if (options.xAxis) {
|
|
if (!options.xAxis.axisLabel) options.xAxis.axisLabel = {};
|
|
options.xAxis.axisLabel.width =
|
|
panelSchema.config.axis_label_truncate_width;
|
|
options.xAxis.axisLabel.overflow = "truncate";
|
|
}
|
|
}
|
|
|
|
// Merge custom chart options if provided (e.g., for alert preview tooltip customization)
|
|
if (panelSchema.config?.custom_chart_options) {
|
|
const customOptions = panelSchema.config.custom_chart_options;
|
|
|
|
// Deep merge custom tooltip options if provided
|
|
if (customOptions.tooltip && options.tooltip) {
|
|
options.tooltip = {
|
|
...options.tooltip,
|
|
...customOptions.tooltip,
|
|
};
|
|
}
|
|
|
|
// Merge any other custom options
|
|
Object.keys(customOptions).forEach((key) => {
|
|
if (key !== "tooltip" && customOptions[key]) {
|
|
options[key] = {
|
|
...(options[key] || {}),
|
|
...customOptions[key],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
options,
|
|
extras: {
|
|
...extras,
|
|
panelId: panelSchema?.id,
|
|
isTimeSeries: isTimeSeriesFlag,
|
|
},
|
|
};
|
|
};
|