mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-24 02:46:27 +00:00
feat: query_range migration from v3/v4 -> v5 (#8192)
* feat: query_range migration from v3/v4 -> v5 * feat: cleanup files * feat: cleanup code * feat: metric payload improvements * feat: metric payload improvements * feat: data retention and qb v2 for dashboard cleanup * feat: corrected datasource change daata updatation in qb v2 * feat: fix value panel plotting with new query v5 * feat: alert migration * feat: fixed aggregation css * feat: explorer pages migration * feat: trace and logs explorer fixes
This commit is contained in:
parent
996080aaf8
commit
aa544f52f3
@ -3,6 +3,7 @@ const apiV1 = '/api/v1/';
|
||||
export const apiV2 = '/api/v2/';
|
||||
export const apiV3 = '/api/v3/';
|
||||
export const apiV4 = '/api/v4/';
|
||||
export const apiV5 = '/api/v5/';
|
||||
export const gatewayApiV1 = '/api/gateway/v1/';
|
||||
export const gatewayApiV2 = '/api/gateway/v2/';
|
||||
export const apiAlertManager = '/api/alertmanager/';
|
||||
|
||||
@ -19,6 +19,7 @@ import apiV1, {
|
||||
apiV2,
|
||||
apiV3,
|
||||
apiV4,
|
||||
apiV5,
|
||||
gatewayApiV1,
|
||||
gatewayApiV2,
|
||||
} from './apiV1';
|
||||
@ -171,6 +172,18 @@ ApiV4Instance.interceptors.response.use(
|
||||
ApiV4Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
//
|
||||
|
||||
// axios V5
|
||||
export const ApiV5Instance = axios.create({
|
||||
baseURL: `${ENVIRONMENT.baseURL}${apiV5}`,
|
||||
});
|
||||
|
||||
ApiV5Instance.interceptors.response.use(
|
||||
interceptorsResponse,
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV5Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
//
|
||||
|
||||
// axios Base
|
||||
export const ApiBaseInstance = axios.create({
|
||||
baseURL: `${ENVIRONMENT.baseURL}${apiV1}`,
|
||||
|
||||
168
frontend/src/api/v5/queryRange/constants.ts
Normal file
168
frontend/src/api/v5/queryRange/constants.ts
Normal file
@ -0,0 +1,168 @@
|
||||
// V5 Query Range Constants
|
||||
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import {
|
||||
FunctionName,
|
||||
RequestType,
|
||||
SignalType,
|
||||
Step,
|
||||
} from 'types/api/v5/queryRange';
|
||||
|
||||
// ===================== Schema and Version Constants =====================
|
||||
|
||||
export const SCHEMA_VERSION_V5 = ENTITY_VERSION_V5;
|
||||
export const API_VERSION_V5 = 'v5';
|
||||
|
||||
// ===================== Default Values =====================
|
||||
|
||||
export const DEFAULT_STEP_INTERVAL: Step = '60s';
|
||||
export const DEFAULT_LIMIT = 100;
|
||||
export const DEFAULT_OFFSET = 0;
|
||||
|
||||
// ===================== Request Type Constants =====================
|
||||
|
||||
export const REQUEST_TYPES: Record<string, RequestType> = {
|
||||
SCALAR: 'scalar',
|
||||
TIME_SERIES: 'time_series',
|
||||
RAW: 'raw',
|
||||
DISTRIBUTION: 'distribution',
|
||||
} as const;
|
||||
|
||||
// ===================== Signal Type Constants =====================
|
||||
|
||||
export const SIGNAL_TYPES: Record<string, SignalType> = {
|
||||
TRACES: 'traces',
|
||||
LOGS: 'logs',
|
||||
METRICS: 'metrics',
|
||||
} as const;
|
||||
|
||||
// ===================== Common Aggregation Expressions =====================
|
||||
|
||||
export const TRACE_AGGREGATIONS = {
|
||||
COUNT: 'count()',
|
||||
COUNT_DISTINCT_TRACE_ID: 'count_distinct(traceID)',
|
||||
AVG_DURATION: 'avg(duration_nano)',
|
||||
P50_DURATION: 'p50(duration_nano)',
|
||||
P95_DURATION: 'p95(duration_nano)',
|
||||
P99_DURATION: 'p99(duration_nano)',
|
||||
MAX_DURATION: 'max(duration_nano)',
|
||||
MIN_DURATION: 'min(duration_nano)',
|
||||
SUM_DURATION: 'sum(duration_nano)',
|
||||
} as const;
|
||||
|
||||
export const LOG_AGGREGATIONS = {
|
||||
COUNT: 'count()',
|
||||
COUNT_DISTINCT_HOST: 'count_distinct(host.name)',
|
||||
COUNT_DISTINCT_SERVICE: 'count_distinct(service.name)',
|
||||
COUNT_DISTINCT_CONTAINER: 'count_distinct(container.name)',
|
||||
} as const;
|
||||
|
||||
// ===================== Common Filter Expressions =====================
|
||||
|
||||
export const COMMON_FILTERS = {
|
||||
// Trace filters
|
||||
SERVER_SPANS: "kind_string = 'Server'",
|
||||
CLIENT_SPANS: "kind_string = 'Client'",
|
||||
INTERNAL_SPANS: "kind_string = 'Internal'",
|
||||
ERROR_SPANS: 'http.status_code >= 400',
|
||||
SUCCESS_SPANS: 'http.status_code < 400',
|
||||
|
||||
// Common service filters
|
||||
EXCLUDE_HEALTH_CHECKS: "http.route != '/health' AND http.route != '/ping'",
|
||||
HTTP_REQUESTS: "http.method != ''",
|
||||
|
||||
// Log filters
|
||||
ERROR_LOGS: "severity_text = 'ERROR'",
|
||||
WARN_LOGS: "severity_text = 'WARN'",
|
||||
INFO_LOGS: "severity_text = 'INFO'",
|
||||
DEBUG_LOGS: "severity_text = 'DEBUG'",
|
||||
} as const;
|
||||
|
||||
// ===================== Common Group By Fields =====================
|
||||
|
||||
export const COMMON_GROUP_BY_FIELDS = {
|
||||
SERVICE_NAME: {
|
||||
name: 'service.name',
|
||||
fieldDataType: 'string' as const,
|
||||
fieldContext: 'resource' as const,
|
||||
},
|
||||
HTTP_METHOD: {
|
||||
name: 'http.method',
|
||||
fieldDataType: 'string' as const,
|
||||
fieldContext: 'attribute' as const,
|
||||
},
|
||||
HTTP_ROUTE: {
|
||||
name: 'http.route',
|
||||
fieldDataType: 'string' as const,
|
||||
fieldContext: 'attribute' as const,
|
||||
},
|
||||
HTTP_STATUS_CODE: {
|
||||
name: 'http.status_code',
|
||||
fieldDataType: 'int64' as const,
|
||||
fieldContext: 'attribute' as const,
|
||||
},
|
||||
HOST_NAME: {
|
||||
name: 'host.name',
|
||||
fieldDataType: 'string' as const,
|
||||
fieldContext: 'resource' as const,
|
||||
},
|
||||
CONTAINER_NAME: {
|
||||
name: 'container.name',
|
||||
fieldDataType: 'string' as const,
|
||||
fieldContext: 'resource' as const,
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ===================== Function Names =====================
|
||||
|
||||
export const FUNCTION_NAMES: Record<string, FunctionName> = {
|
||||
CUT_OFF_MIN: 'cutOffMin',
|
||||
CUT_OFF_MAX: 'cutOffMax',
|
||||
CLAMP_MIN: 'clampMin',
|
||||
CLAMP_MAX: 'clampMax',
|
||||
ABSOLUTE: 'absolute',
|
||||
RUNNING_DIFF: 'runningDiff',
|
||||
LOG2: 'log2',
|
||||
LOG10: 'log10',
|
||||
CUM_SUM: 'cumSum',
|
||||
EWMA3: 'ewma3',
|
||||
EWMA5: 'ewma5',
|
||||
EWMA7: 'ewma7',
|
||||
MEDIAN3: 'median3',
|
||||
MEDIAN5: 'median5',
|
||||
MEDIAN7: 'median7',
|
||||
TIME_SHIFT: 'timeShift',
|
||||
ANOMALY: 'anomaly',
|
||||
} as const;
|
||||
|
||||
// ===================== Common Step Intervals =====================
|
||||
|
||||
export const STEP_INTERVALS = {
|
||||
FIFTEEN_SECONDS: '15s',
|
||||
THIRTY_SECONDS: '30s',
|
||||
ONE_MINUTE: '60s',
|
||||
FIVE_MINUTES: '300s',
|
||||
TEN_MINUTES: '600s',
|
||||
FIFTEEN_MINUTES: '900s',
|
||||
THIRTY_MINUTES: '1800s',
|
||||
ONE_HOUR: '3600s',
|
||||
TWO_HOURS: '7200s',
|
||||
SIX_HOURS: '21600s',
|
||||
TWELVE_HOURS: '43200s',
|
||||
ONE_DAY: '86400s',
|
||||
} as const;
|
||||
|
||||
// ===================== Time Range Presets =====================
|
||||
|
||||
export const TIME_RANGE_PRESETS = {
|
||||
LAST_5_MINUTES: 5 * 60 * 1000,
|
||||
LAST_15_MINUTES: 15 * 60 * 1000,
|
||||
LAST_30_MINUTES: 30 * 60 * 1000,
|
||||
LAST_HOUR: 60 * 60 * 1000,
|
||||
LAST_3_HOURS: 3 * 60 * 60 * 1000,
|
||||
LAST_6_HOURS: 6 * 60 * 60 * 1000,
|
||||
LAST_12_HOURS: 12 * 60 * 60 * 1000,
|
||||
LAST_24_HOURS: 24 * 60 * 60 * 1000,
|
||||
LAST_3_DAYS: 3 * 24 * 60 * 60 * 1000,
|
||||
LAST_7_DAYS: 7 * 24 * 60 * 60 * 1000,
|
||||
} as const;
|
||||
358
frontend/src/api/v5/queryRange/convertV5Response.ts
Normal file
358
frontend/src/api/v5/queryRange/convertV5Response.ts
Normal file
@ -0,0 +1,358 @@
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadV3 } from 'types/api/metrics/getQueryRange';
|
||||
import {
|
||||
DistributionData,
|
||||
MetricRangePayloadV5,
|
||||
RawData,
|
||||
ScalarData,
|
||||
TimeSeriesData,
|
||||
} from 'types/api/v5/queryRange';
|
||||
import { QueryDataV3 } from 'types/api/widgets/getQuery';
|
||||
|
||||
/**
|
||||
* Converts V5 TimeSeriesData to legacy format
|
||||
*/
|
||||
function convertTimeSeriesData(
|
||||
timeSeriesData: TimeSeriesData,
|
||||
legendMap: Record<string, string>,
|
||||
): QueryDataV3 {
|
||||
// Convert V5 time series format to legacy QueryDataV3 format
|
||||
return {
|
||||
queryName: timeSeriesData.queryName,
|
||||
legend: legendMap[timeSeriesData.queryName] || timeSeriesData.queryName,
|
||||
series: timeSeriesData.aggregations.flatMap((aggregation) =>
|
||||
aggregation.series.map((series) => ({
|
||||
labels: series.labels
|
||||
? Object.fromEntries(
|
||||
series.labels.map((label) => [label.key.name, label.value]),
|
||||
)
|
||||
: {},
|
||||
labelsArray: series.labels
|
||||
? series.labels.map((label) => ({ [label.key.name]: label.value }))
|
||||
: [],
|
||||
values: series.values.map((value) => ({
|
||||
timestamp: value.timestamp,
|
||||
value: String(value.value),
|
||||
})),
|
||||
})),
|
||||
),
|
||||
list: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to collect columns from scalar data
|
||||
*/
|
||||
function collectColumnsFromScalarData(
|
||||
scalarData: ScalarData[],
|
||||
): { name: string; queryName: string; isValueColumn: boolean }[] {
|
||||
const columnMap = new Map<
|
||||
string,
|
||||
{ name: string; queryName: string; isValueColumn: boolean }
|
||||
>();
|
||||
|
||||
scalarData.forEach((scalar) => {
|
||||
scalar.columns.forEach((col) => {
|
||||
if (col.columnType === 'group') {
|
||||
// For group columns, use the column name as-is
|
||||
const key = `${col.name}_group`;
|
||||
if (!columnMap.has(key)) {
|
||||
columnMap.set(key, {
|
||||
name: col.name,
|
||||
queryName: '', // Group columns don't have query names
|
||||
isValueColumn: false,
|
||||
});
|
||||
}
|
||||
} else if (col.columnType === 'aggregation') {
|
||||
// For aggregation columns, use the query name as the column name
|
||||
const key = `${col.queryName}_aggregation`;
|
||||
if (!columnMap.has(key)) {
|
||||
columnMap.set(key, {
|
||||
name: col.queryName, // Use query name as column name (A, B, etc.)
|
||||
queryName: col.queryName,
|
||||
isValueColumn: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(columnMap.values()).sort((a, b) => {
|
||||
if (a.isValueColumn !== b.isValueColumn) {
|
||||
return a.isValueColumn ? 1 : -1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to process scalar data rows with unified table structure
|
||||
*/
|
||||
function processScalarDataRows(
|
||||
scalarData: ScalarData[],
|
||||
): { data: Record<string, any> }[] {
|
||||
// First, identify all group columns and all value columns
|
||||
const allGroupColumns = new Set<string>();
|
||||
const allValueColumns = new Set<string>();
|
||||
|
||||
scalarData.forEach((scalar) => {
|
||||
scalar.columns.forEach((col) => {
|
||||
if (col.columnType === 'group') {
|
||||
allGroupColumns.add(col.name);
|
||||
} else if (col.columnType === 'aggregation') {
|
||||
// Use query name for value columns to match expected format
|
||||
allValueColumns.add(col.queryName);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Create a unified row structure
|
||||
const unifiedRows = new Map<string, Record<string, any>>();
|
||||
|
||||
// Process each scalar result
|
||||
scalarData.forEach((scalar) => {
|
||||
scalar.data.forEach((dataRow) => {
|
||||
const groupColumns = scalar.columns.filter(
|
||||
(col) => col.columnType === 'group',
|
||||
);
|
||||
|
||||
// Create row key based on group columns
|
||||
let rowKey: string;
|
||||
const groupValues: Record<string, any> = {};
|
||||
|
||||
if (groupColumns.length > 0) {
|
||||
const keyParts: string[] = [];
|
||||
groupColumns.forEach((col, index) => {
|
||||
const value = dataRow[index];
|
||||
keyParts.push(String(value));
|
||||
groupValues[col.name] = value;
|
||||
});
|
||||
rowKey = keyParts.join('|');
|
||||
} else {
|
||||
// For scalar values without grouping, create a default row
|
||||
rowKey = 'default_row';
|
||||
// Set all group columns to 'n/a' for this row
|
||||
Array.from(allGroupColumns).forEach((groupCol) => {
|
||||
groupValues[groupCol] = 'n/a';
|
||||
});
|
||||
}
|
||||
|
||||
// Get or create the unified row
|
||||
if (!unifiedRows.has(rowKey)) {
|
||||
const newRow: Record<string, any> = { ...groupValues };
|
||||
// Initialize all value columns to 'n/a'
|
||||
Array.from(allValueColumns).forEach((valueCol) => {
|
||||
newRow[valueCol] = 'n/a';
|
||||
});
|
||||
unifiedRows.set(rowKey, newRow);
|
||||
}
|
||||
|
||||
const row = unifiedRows.get(rowKey)!;
|
||||
|
||||
// Fill in the aggregation values using query name as column name
|
||||
scalar.columns.forEach((col, colIndex) => {
|
||||
if (col.columnType === 'aggregation') {
|
||||
row[col.queryName] = dataRow[colIndex];
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(unifiedRows.values()).map((rowData) => ({
|
||||
data: rowData,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts V5 ScalarData array to legacy format with table structure
|
||||
*/
|
||||
function convertScalarDataArrayToTable(
|
||||
scalarDataArray: ScalarData[],
|
||||
legendMap: Record<string, string>,
|
||||
): QueryDataV3 {
|
||||
// If no scalar data, return empty structure
|
||||
if (!scalarDataArray || scalarDataArray.length === 0) {
|
||||
return {
|
||||
queryName: '',
|
||||
legend: '',
|
||||
series: null,
|
||||
list: null,
|
||||
table: {
|
||||
columns: [],
|
||||
rows: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Collect columns and process rows
|
||||
const columns = collectColumnsFromScalarData(scalarDataArray);
|
||||
const rows = processScalarDataRows(scalarDataArray);
|
||||
|
||||
// Get the primary query name
|
||||
const primaryQuery = scalarDataArray.find((s) =>
|
||||
s.columns.some((c) => c.columnType === 'aggregation'),
|
||||
);
|
||||
const queryName =
|
||||
primaryQuery?.columns.find((c) => c.columnType === 'aggregation')
|
||||
?.queryName ||
|
||||
scalarDataArray[0]?.columns[0]?.queryName ||
|
||||
'';
|
||||
|
||||
return {
|
||||
queryName,
|
||||
legend: legendMap[queryName] || queryName,
|
||||
series: null,
|
||||
list: null,
|
||||
table: {
|
||||
columns,
|
||||
rows,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts V5 RawData to legacy format
|
||||
*/
|
||||
function convertRawData(
|
||||
rawData: RawData,
|
||||
legendMap: Record<string, string>,
|
||||
): QueryDataV3 {
|
||||
// Convert V5 raw format to legacy QueryDataV3 format
|
||||
return {
|
||||
queryName: rawData.queryName,
|
||||
legend: legendMap[rawData.queryName] || rawData.queryName,
|
||||
series: null,
|
||||
list: rawData.rows?.map((row) => ({
|
||||
timestamp: row.timestamp,
|
||||
data: {
|
||||
// Map raw data to ILog structure - spread row.data first to include all properties
|
||||
...row.data,
|
||||
date: row.timestamp,
|
||||
} as any,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts V5 DistributionData to legacy format
|
||||
*/
|
||||
function convertDistributionData(
|
||||
distributionData: DistributionData,
|
||||
legendMap: Record<string, string>,
|
||||
): any {
|
||||
// eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
// Convert V5 distribution format to legacy histogram format
|
||||
return {
|
||||
...distributionData,
|
||||
legendMap,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert V5 data based on type
|
||||
*/
|
||||
function convertV5DataByType(
|
||||
v5Data: any,
|
||||
legendMap: Record<string, string>,
|
||||
): MetricRangePayloadV3['data'] {
|
||||
switch (v5Data?.type) {
|
||||
case 'time_series': {
|
||||
const timeSeriesData = v5Data.data.results as TimeSeriesData[];
|
||||
return {
|
||||
resultType: 'time_series',
|
||||
result: timeSeriesData.map((timeSeries) =>
|
||||
convertTimeSeriesData(timeSeries, legendMap),
|
||||
),
|
||||
};
|
||||
}
|
||||
case 'scalar': {
|
||||
const scalarData = v5Data.data.results as ScalarData[];
|
||||
// For scalar data, combine all results into a single table
|
||||
const combinedTable = convertScalarDataArrayToTable(scalarData, legendMap);
|
||||
return {
|
||||
resultType: 'scalar',
|
||||
result: [combinedTable],
|
||||
};
|
||||
}
|
||||
case 'raw': {
|
||||
const rawData = v5Data.data.results as RawData[];
|
||||
return {
|
||||
resultType: 'raw',
|
||||
result: rawData.map((raw) => convertRawData(raw, legendMap)),
|
||||
};
|
||||
}
|
||||
case 'distribution': {
|
||||
const distributionData = v5Data.data.results as DistributionData[];
|
||||
return {
|
||||
resultType: 'distribution',
|
||||
result: distributionData.map((distribution) =>
|
||||
convertDistributionData(distribution, legendMap),
|
||||
),
|
||||
};
|
||||
}
|
||||
default:
|
||||
return {
|
||||
resultType: '',
|
||||
result: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts V5 API response to legacy format expected by frontend components
|
||||
*/
|
||||
export function convertV5ResponseToLegacy(
|
||||
v5Response: SuccessResponse<MetricRangePayloadV5>,
|
||||
legendMap: Record<string, string>,
|
||||
// formatForWeb?: boolean,
|
||||
): SuccessResponse<MetricRangePayloadV3> {
|
||||
const { payload } = v5Response;
|
||||
const v5Data = payload?.data;
|
||||
|
||||
// todo - sagar
|
||||
// If formatForWeb is true, return as-is (like existing logic)
|
||||
// Exception: scalar data should always be converted to table format
|
||||
// if (formatForWeb && v5Data?.type !== 'scalar') {
|
||||
// return v5Response as any;
|
||||
// }
|
||||
|
||||
// Convert based on V5 response type
|
||||
const convertedData = convertV5DataByType(v5Data, legendMap);
|
||||
|
||||
// Create legacy-compatible response structure
|
||||
const legacyResponse: SuccessResponse<MetricRangePayloadV3> = {
|
||||
...v5Response,
|
||||
payload: {
|
||||
data: convertedData,
|
||||
},
|
||||
};
|
||||
|
||||
// Apply legend mapping (similar to existing logic)
|
||||
if (legacyResponse.payload?.data?.result) {
|
||||
legacyResponse.payload.data.result = legacyResponse.payload.data.result.map(
|
||||
(queryData: any) => {
|
||||
// eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
const newQueryData = queryData;
|
||||
newQueryData.legend = legendMap[queryData.queryName];
|
||||
|
||||
// If metric names is an empty object
|
||||
if (isEmpty(queryData.metric)) {
|
||||
// If metrics list is empty && the user haven't defined a legend then add the legend equal to the name of the query.
|
||||
if (!newQueryData.legend) {
|
||||
newQueryData.legend = queryData.queryName;
|
||||
}
|
||||
// If name of the query and the legend if inserted is same then add the same to the metrics object.
|
||||
if (queryData.queryName === newQueryData.legend) {
|
||||
newQueryData.metric = newQueryData.metric || {};
|
||||
newQueryData.metric[queryData.queryName] = queryData.queryName;
|
||||
}
|
||||
}
|
||||
|
||||
return newQueryData;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return legacyResponse;
|
||||
}
|
||||
51
frontend/src/api/v5/queryRange/getQueryRange.ts
Normal file
51
frontend/src/api/v5/queryRange/getQueryRange.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { ApiV5Instance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
MetricRangePayloadV5,
|
||||
QueryRangePayloadV5,
|
||||
} from 'types/api/v5/queryRange';
|
||||
|
||||
export const getQueryRangeV5 = async (
|
||||
props: QueryRangePayloadV5,
|
||||
version: string,
|
||||
signal: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<SuccessResponse<MetricRangePayloadV5> | ErrorResponse> => {
|
||||
try {
|
||||
if (version && version === ENTITY_VERSION_V5) {
|
||||
const response = await ApiV5Instance.post('/query_range', props, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
params: props,
|
||||
};
|
||||
}
|
||||
|
||||
// Default V5 behavior
|
||||
const response = await ApiV5Instance.post('/query_range', props, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
params: props,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getQueryRangeV5;
|
||||
384
frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts
Normal file
384
frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts
Normal file
@ -0,0 +1,384 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
QueryFunctionProps,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
BaseBuilderQuery,
|
||||
FieldContext,
|
||||
FieldDataType,
|
||||
FunctionName,
|
||||
GroupByKey,
|
||||
LogAggregation,
|
||||
MetricAggregation,
|
||||
OrderBy,
|
||||
QueryEnvelope,
|
||||
QueryFunction,
|
||||
QueryRangePayloadV5,
|
||||
QueryType,
|
||||
RequestType,
|
||||
TelemetryFieldKey,
|
||||
TraceAggregation,
|
||||
} from 'types/api/v5/queryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
type PrepareQueryRangePayloadV5Result = {
|
||||
queryPayload: QueryRangePayloadV5;
|
||||
legendMap: Record<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps panel types to V5 request types
|
||||
*/
|
||||
function mapPanelTypeToRequestType(panelType: PANEL_TYPES): RequestType {
|
||||
switch (panelType) {
|
||||
case PANEL_TYPES.TIME_SERIES:
|
||||
case PANEL_TYPES.BAR:
|
||||
return 'time_series';
|
||||
case PANEL_TYPES.TABLE:
|
||||
case PANEL_TYPES.PIE:
|
||||
case PANEL_TYPES.VALUE:
|
||||
case PANEL_TYPES.TRACE:
|
||||
return 'scalar';
|
||||
case PANEL_TYPES.LIST:
|
||||
return 'raw';
|
||||
case PANEL_TYPES.HISTOGRAM:
|
||||
return 'distribution';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets signal type from data source
|
||||
*/
|
||||
function getSignalType(dataSource: string): 'traces' | 'logs' | 'metrics' {
|
||||
if (dataSource === 'traces') return 'traces';
|
||||
if (dataSource === 'logs') return 'logs';
|
||||
return 'metrics';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates base spec for builder queries
|
||||
*/
|
||||
function createBaseSpec(
|
||||
queryData: IBuilderQuery,
|
||||
requestType: RequestType,
|
||||
): BaseBuilderQuery {
|
||||
return {
|
||||
stepInterval: queryData.stepInterval,
|
||||
disabled: queryData.disabled,
|
||||
filter: queryData?.filter?.expression ? queryData.filter : undefined,
|
||||
groupBy:
|
||||
queryData.groupBy?.length > 0
|
||||
? queryData.groupBy.map(
|
||||
(item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType,
|
||||
fieldContext: item?.type,
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}),
|
||||
)
|
||||
: undefined,
|
||||
limit: isEmpty(queryData.limit)
|
||||
? queryData?.pageSize ?? undefined
|
||||
: queryData.limit ?? undefined,
|
||||
offset: requestType === 'raw' ? queryData.offset : undefined,
|
||||
order:
|
||||
queryData.orderBy.length > 0
|
||||
? queryData.orderBy.map(
|
||||
(order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}),
|
||||
)
|
||||
: undefined,
|
||||
// legend: isEmpty(queryData.legend) ? undefined : queryData.legend,
|
||||
having: isEmpty(queryData.havingExpression)
|
||||
? undefined
|
||||
: queryData?.havingExpression,
|
||||
functions: isEmpty(queryData.functions)
|
||||
? undefined
|
||||
: queryData.functions.map(
|
||||
(func: QueryFunctionProps): QueryFunction => ({
|
||||
name: func.name as FunctionName,
|
||||
args: func.args.map((arg) => ({
|
||||
// name: arg.name,
|
||||
value: arg,
|
||||
})),
|
||||
}),
|
||||
),
|
||||
selectFields: isEmpty(queryData.selectColumns)
|
||||
? undefined
|
||||
: queryData.selectColumns?.map(
|
||||
(column: BaseAutocompleteData): TelemetryFieldKey => ({
|
||||
name: column.key,
|
||||
fieldDataType: column?.dataType as FieldDataType,
|
||||
fieldContext: column?.type as FieldContext,
|
||||
}),
|
||||
),
|
||||
};
|
||||
}
|
||||
// Utility to parse aggregation expressions with optional alias
|
||||
export function parseAggregations(
|
||||
expression: string,
|
||||
): { expression: string; alias?: string }[] {
|
||||
const result: { expression: string; alias?: string }[] = [];
|
||||
const regex = /([a-zA-Z0-9_]+\([^)]*\))(?:\s*as\s+([a-zA-Z0-9_]+))?/g;
|
||||
let match = regex.exec(expression);
|
||||
while (match !== null) {
|
||||
const expr = match[1];
|
||||
const alias = match[2];
|
||||
if (alias) {
|
||||
result.push({ expression: expr, alias });
|
||||
} else {
|
||||
result.push({ expression: expr });
|
||||
}
|
||||
match = regex.exec(expression);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function createAggregation(
|
||||
queryData: any,
|
||||
): TraceAggregation[] | LogAggregation[] | MetricAggregation[] {
|
||||
if (queryData.dataSource === DataSource.METRICS) {
|
||||
return [
|
||||
{
|
||||
metricName: queryData?.aggregateAttribute?.key,
|
||||
temporality: queryData?.aggregateAttribute?.temporality,
|
||||
timeAggregation: queryData?.timeAggregation,
|
||||
spaceAggregation: queryData?.spaceAggregation,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (queryData.aggregations?.length > 0) {
|
||||
return isEmpty(parseAggregations(queryData.aggregations?.[0].expression))
|
||||
? [{ expression: 'count()' }]
|
||||
: parseAggregations(queryData.aggregations?.[0].expression);
|
||||
}
|
||||
|
||||
return [{ expression: 'count()' }];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts query builder data to V5 builder queries
|
||||
*/
|
||||
function convertBuilderQueriesToV5(
|
||||
builderQueries: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
requestType: RequestType,
|
||||
): QueryEnvelope[] {
|
||||
return Object.entries(builderQueries).map(
|
||||
([queryName, queryData]): QueryEnvelope => {
|
||||
const signal = getSignalType(queryData.dataSource);
|
||||
const baseSpec = createBaseSpec(queryData, requestType);
|
||||
let spec: QueryEnvelope['spec'];
|
||||
|
||||
const aggregations = createAggregation(queryData);
|
||||
|
||||
switch (signal) {
|
||||
case 'traces':
|
||||
spec = {
|
||||
name: queryName,
|
||||
signal: 'traces' as const,
|
||||
...baseSpec,
|
||||
aggregations: aggregations as TraceAggregation[],
|
||||
limit: baseSpec?.limit ?? (requestType === 'raw' ? 10 : undefined),
|
||||
};
|
||||
break;
|
||||
case 'logs':
|
||||
spec = {
|
||||
name: queryName,
|
||||
signal: 'logs' as const,
|
||||
...baseSpec,
|
||||
aggregations: aggregations as LogAggregation[],
|
||||
limit: baseSpec?.limit ?? (requestType === 'raw' ? 10 : undefined),
|
||||
};
|
||||
break;
|
||||
case 'metrics':
|
||||
default:
|
||||
spec = {
|
||||
name: queryName,
|
||||
signal: 'metrics' as const,
|
||||
...baseSpec,
|
||||
aggregations: aggregations as MetricAggregation[],
|
||||
// reduceTo: queryData.reduceTo,
|
||||
limit: baseSpec?.limit ?? (requestType === 'raw' ? 10 : undefined),
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'builder_query' as QueryType,
|
||||
spec,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts PromQL queries to V5 format
|
||||
*/
|
||||
function convertPromQueriesToV5(
|
||||
promQueries: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): QueryEnvelope[] {
|
||||
return Object.entries(promQueries).map(
|
||||
([queryName, queryData]): QueryEnvelope => ({
|
||||
type: 'promql' as QueryType,
|
||||
spec: {
|
||||
name: queryName,
|
||||
query: queryData.query,
|
||||
disabled: queryData.disabled || false,
|
||||
step: queryData.stepInterval,
|
||||
stats: false, // PromQL specific field
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts ClickHouse queries to V5 format
|
||||
*/
|
||||
function convertClickHouseQueriesToV5(
|
||||
chQueries: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): QueryEnvelope[] {
|
||||
return Object.entries(chQueries).map(
|
||||
([queryName, queryData]): QueryEnvelope => ({
|
||||
type: 'clickhouse_sql' as QueryType,
|
||||
spec: {
|
||||
name: queryName,
|
||||
query: queryData.query,
|
||||
disabled: queryData.disabled || false,
|
||||
// ClickHouse doesn't have step or stats like PromQL
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts query formulas to V5 format
|
||||
*/
|
||||
function convertFormulasToV5(
|
||||
formulas: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): QueryEnvelope[] {
|
||||
return Object.entries(formulas).map(
|
||||
([queryName, formulaData]): QueryEnvelope => ({
|
||||
type: 'builder_formula' as QueryType,
|
||||
spec: {
|
||||
name: queryName,
|
||||
expression: formulaData.expression || '',
|
||||
functions: formulaData.functions,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to reduce query arrays to objects
|
||||
*/
|
||||
function reduceQueriesToObject(
|
||||
queryArray: any[], // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): { queries: Record<string, any>; legends: Record<string, string> } {
|
||||
// eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
const legends: Record<string, string> = {};
|
||||
const queries = queryArray.reduce((acc, queryItem) => {
|
||||
if (!queryItem.query) return acc;
|
||||
acc[queryItem.name] = queryItem;
|
||||
legends[queryItem.name] = queryItem.legend;
|
||||
return acc;
|
||||
}, {} as Record<string, any>); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
return { queries, legends };
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares V5 query range payload from GetQueryResultsProps
|
||||
*/
|
||||
export const prepareQueryRangePayloadV5 = ({
|
||||
query,
|
||||
globalSelectedInterval,
|
||||
graphType,
|
||||
selectedTime,
|
||||
tableParams,
|
||||
variables = {},
|
||||
start: startTime,
|
||||
end: endTime,
|
||||
}: GetQueryResultsProps): PrepareQueryRangePayloadV5Result => {
|
||||
let legendMap: Record<string, string> = {};
|
||||
const requestType = mapPanelTypeToRequestType(graphType);
|
||||
let queries: QueryEnvelope[] = [];
|
||||
|
||||
switch (query.queryType) {
|
||||
case EQueryType.QUERY_BUILDER: {
|
||||
const { queryData: data, queryFormulas } = query.builder;
|
||||
const currentQueryData = mapQueryDataToApi(data, 'queryName', tableParams);
|
||||
const currentFormulas = mapQueryDataToApi(queryFormulas, 'queryName');
|
||||
|
||||
// Combine legend maps
|
||||
legendMap = {
|
||||
...currentQueryData.newLegendMap,
|
||||
...currentFormulas.newLegendMap,
|
||||
};
|
||||
|
||||
// Convert builder queries
|
||||
const builderQueries = convertBuilderQueriesToV5(
|
||||
currentQueryData.data,
|
||||
requestType,
|
||||
);
|
||||
|
||||
// Convert formulas as separate query type
|
||||
const formulaQueries = convertFormulasToV5(currentFormulas.data);
|
||||
|
||||
// Combine both types
|
||||
queries = [...builderQueries, ...formulaQueries];
|
||||
break;
|
||||
}
|
||||
case EQueryType.PROM: {
|
||||
const promQueries = reduceQueriesToObject(query[query.queryType]);
|
||||
queries = convertPromQueriesToV5(promQueries.queries);
|
||||
legendMap = promQueries.legends;
|
||||
break;
|
||||
}
|
||||
case EQueryType.CLICKHOUSE: {
|
||||
const chQueries = reduceQueriesToObject(query[query.queryType]);
|
||||
queries = convertClickHouseQueriesToV5(chQueries.queries);
|
||||
legendMap = chQueries.legends;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate time range
|
||||
const { start, end } = getStartEndRangeTime({
|
||||
type: selectedTime,
|
||||
interval: globalSelectedInterval,
|
||||
});
|
||||
|
||||
// Create V5 payload
|
||||
const queryPayload: QueryRangePayloadV5 = {
|
||||
schemaVersion: 'v1',
|
||||
start: startTime ? startTime * 1e3 : parseInt(start, 10) * 1e3,
|
||||
end: endTime ? endTime * 1e3 : parseInt(end, 10) * 1e3,
|
||||
requestType,
|
||||
compositeQuery: {
|
||||
queries,
|
||||
},
|
||||
variables,
|
||||
};
|
||||
|
||||
return { legendMap, queryPayload };
|
||||
};
|
||||
8
frontend/src/api/v5/v5.ts
Normal file
8
frontend/src/api/v5/v5.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// V5 API exports
|
||||
export * from './queryRange/constants';
|
||||
export { convertV5ResponseToLegacy } from './queryRange/convertV5Response';
|
||||
export { getQueryRangeV5 } from './queryRange/getQueryRange';
|
||||
export { prepareQueryRangePayloadV5 } from './queryRange/prepareQueryRangePayloadV5';
|
||||
|
||||
// Export types from proper location
|
||||
export * from 'types/api/v5/queryRange';
|
||||
@ -15,6 +15,7 @@ import { Button } from 'antd';
|
||||
import { useQueryBuilderV2Context } from 'components/QueryBuilderV2/QueryBuilderV2Context';
|
||||
import { X } from 'lucide-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
const havingOperators = [
|
||||
{
|
||||
@ -67,16 +68,30 @@ const conjunctions = [
|
||||
{ label: 'OR', value: 'OR' },
|
||||
];
|
||||
|
||||
function HavingFilter({ onClose }: { onClose: () => void }): JSX.Element {
|
||||
function HavingFilter({
|
||||
onClose,
|
||||
onChange,
|
||||
queryData,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onChange: (value: string) => void;
|
||||
queryData: IBuilderQuery;
|
||||
}): JSX.Element {
|
||||
const { aggregationOptions } = useQueryBuilderV2Context();
|
||||
const [input, setInput] = useState('');
|
||||
const [input, setInput] = useState(
|
||||
queryData?.havingExpression?.expression || '',
|
||||
);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const editorRef = useRef<EditorView | null>(null);
|
||||
|
||||
const [options, setOptions] = useState<{ label: string; value: string }[]>([]);
|
||||
|
||||
// Effect to handle focus state and trigger suggestions
|
||||
const handleChange = (value: string): void => {
|
||||
setInput(value);
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused && editorRef.current && options.length > 0) {
|
||||
startCompletion(editorRef.current);
|
||||
@ -237,9 +252,10 @@ function HavingFilter({ onClose }: { onClose: () => void }): JSX.Element {
|
||||
<div className="having-filter-select-container">
|
||||
<CodeMirror
|
||||
value={input}
|
||||
onChange={handleChange}
|
||||
theme={copilot}
|
||||
onChange={setInput}
|
||||
className="having-filter-select-editor"
|
||||
width="100%"
|
||||
extensions={[
|
||||
havingAutocomplete,
|
||||
javascript({ jsx: false, typescript: false }),
|
||||
|
||||
@ -7,6 +7,7 @@ import { GroupByFilter } from 'container/QueryBuilder/filters/GroupByFilter/Grou
|
||||
import { OrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter';
|
||||
import { ReduceToFilter } from 'container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { BarChart2, ScrollText, X } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@ -98,22 +99,38 @@ function QueryAddOns({
|
||||
return;
|
||||
}
|
||||
|
||||
let filteredAddOns: AddOn[];
|
||||
if (panelType === PANEL_TYPES.VALUE) {
|
||||
// Filter out all add-ons except legend format
|
||||
setAddOns((prevAddOns) =>
|
||||
prevAddOns.filter((addOn) => addOn.key === ADD_ONS_KEYS.LEGEND_FORMAT),
|
||||
filteredAddOns = ADD_ONS.filter(
|
||||
(addOn) => addOn.key === ADD_ONS_KEYS.LEGEND_FORMAT,
|
||||
);
|
||||
} else {
|
||||
setAddOns(Object.values(ADD_ONS));
|
||||
filteredAddOns = Object.values(ADD_ONS);
|
||||
|
||||
// Filter out group_by for metrics data source
|
||||
if (query.dataSource === DataSource.METRICS) {
|
||||
filteredAddOns = filteredAddOns.filter(
|
||||
(addOn) => addOn.key !== ADD_ONS_KEYS.GROUP_BY,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// add reduce to if showReduceTo is true
|
||||
if (showReduceTo) {
|
||||
setAddOns((prevAddOns) => [...prevAddOns, REDUCE_TO]);
|
||||
filteredAddOns = [...filteredAddOns, REDUCE_TO];
|
||||
}
|
||||
|
||||
setAddOns(filteredAddOns);
|
||||
|
||||
// Filter selectedViews to only include add-ons present in filteredAddOns
|
||||
setSelectedViews((prevSelectedViews) =>
|
||||
prevSelectedViews.filter((view) =>
|
||||
filteredAddOns.some((addOn) => addOn.key === view.key),
|
||||
),
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [panelType, isListViewPanel]);
|
||||
}, [panelType, isListViewPanel, query.dataSource]);
|
||||
|
||||
const handleOptionClick = (e: RadioChangeEvent): void => {
|
||||
if (selectedViews.find((view) => view.key === e.target.value.key)) {
|
||||
@ -167,6 +184,15 @@ function QueryAddOns({
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
const handleChangeHaving = useCallback(
|
||||
(value: string) => {
|
||||
handleChangeQueryData('havingExpression', {
|
||||
expression: value,
|
||||
});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="query-add-ons">
|
||||
{selectedViews.length > 0 && (
|
||||
@ -204,6 +230,8 @@ function QueryAddOns({
|
||||
selectedViews.filter((view) => view.key !== 'having'),
|
||||
);
|
||||
}}
|
||||
onChange={handleChangeHaving}
|
||||
queryData={query}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -214,6 +242,7 @@ function QueryAddOns({
|
||||
<InputWithLabel
|
||||
label="Limit"
|
||||
onChange={handleChangeLimit}
|
||||
initialValue={query?.limit ?? undefined}
|
||||
placeholder="Enter limit"
|
||||
onClose={(): void => {
|
||||
setSelectedViews(selectedViews.filter((view) => view.key !== 'limit'));
|
||||
@ -233,11 +262,13 @@ function QueryAddOns({
|
||||
isListViewPanel={isListViewPanel}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="close-btn periscope-btn ghost"
|
||||
icon={<X size={16} />}
|
||||
onClick={(): void => handleRemoveView('order_by')}
|
||||
/>
|
||||
{!isListViewPanel && (
|
||||
<Button
|
||||
className="close-btn periscope-btn ghost"
|
||||
icon={<X size={16} />}
|
||||
onClick={(): void => handleRemoveView('order_by')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -265,6 +296,7 @@ function QueryAddOns({
|
||||
label="Legend format"
|
||||
placeholder="Write legend format"
|
||||
onChange={handleChangeQueryLegend}
|
||||
initialValue={isEmpty(query?.legend) ? undefined : query?.legend}
|
||||
onClose={(): void => {
|
||||
setSelectedViews(
|
||||
selectedViews.filter((view) => view.key !== 'legend_format'),
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 400px;
|
||||
|
||||
.query-aggregation-select-editor {
|
||||
border-radius: 2px;
|
||||
|
||||
@ -3,6 +3,7 @@ import './QueryAggregation.styles.scss';
|
||||
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useMemo } from 'react';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import QueryAggregationSelect from './QueryAggregationSelect';
|
||||
@ -11,10 +12,14 @@ function QueryAggregationOptions({
|
||||
dataSource,
|
||||
panelType,
|
||||
onAggregationIntervalChange,
|
||||
onChange,
|
||||
queryData,
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
panelType?: string;
|
||||
onAggregationIntervalChange: (value: number) => void;
|
||||
onChange?: (value: string) => void;
|
||||
queryData: IBuilderQuery;
|
||||
}): JSX.Element {
|
||||
const showAggregationInterval = useMemo(() => {
|
||||
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
|
||||
@ -36,14 +41,14 @@ function QueryAggregationOptions({
|
||||
return (
|
||||
<div className="query-aggregation-container">
|
||||
<div className="aggregation-container">
|
||||
<QueryAggregationSelect />
|
||||
<QueryAggregationSelect onChange={onChange} queryData={queryData} />
|
||||
|
||||
{showAggregationInterval && (
|
||||
<div className="query-aggregation-interval">
|
||||
<div className="query-aggregation-interval-label">every</div>
|
||||
<div className="query-aggregation-interval-input-container">
|
||||
<InputWithLabel
|
||||
initialValue="60"
|
||||
initialValue={queryData.stepInterval ? queryData.stepInterval : '60'}
|
||||
className="query-aggregation-interval-input"
|
||||
label="Seconds"
|
||||
placeholder="60"
|
||||
@ -62,6 +67,7 @@ function QueryAggregationOptions({
|
||||
|
||||
QueryAggregationOptions.defaultProps = {
|
||||
panelType: null,
|
||||
onChange: undefined,
|
||||
};
|
||||
|
||||
export default QueryAggregationOptions;
|
||||
|
||||
@ -28,10 +28,10 @@ import CodeMirror, {
|
||||
import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute';
|
||||
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||
import { tracesAggregateOperatorOptions } from 'constants/queryBuilderOperators';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { TracesAggregatorOperator } from 'types/common/queryBuilder';
|
||||
|
||||
import { useQueryBuilderV2Context } from '../../QueryBuilderV2Context';
|
||||
@ -113,11 +113,18 @@ function getFunctionContextAtCursor(
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/no-this-in-sfc
|
||||
function QueryAggregationSelect(): JSX.Element {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
function QueryAggregationSelect({
|
||||
onChange,
|
||||
queryData,
|
||||
}: {
|
||||
onChange?: (value: string) => void;
|
||||
queryData: IBuilderQuery;
|
||||
}): JSX.Element {
|
||||
const { setAggregationOptions } = useQueryBuilderV2Context();
|
||||
const queryData = currentQuery.builder.queryData[0];
|
||||
const [input, setInput] = useState('');
|
||||
|
||||
const [input, setInput] = useState(
|
||||
queryData?.aggregations?.map((i: any) => i.expression).join(' ') || '',
|
||||
);
|
||||
const [cursorPos, setCursorPos] = useState(0);
|
||||
const [functionArgPairs, setFunctionArgPairs] = useState<
|
||||
{ func: string; arg: string }[]
|
||||
@ -416,7 +423,10 @@ function QueryAggregationSelect(): JSX.Element {
|
||||
<div className="query-aggregation-select-container">
|
||||
<CodeMirror
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
onChange={(value): void => {
|
||||
setInput(value);
|
||||
onChange?.(value);
|
||||
}}
|
||||
className="query-aggregation-select-editor"
|
||||
theme={copilot}
|
||||
extensions={[
|
||||
@ -458,4 +468,8 @@ function QueryAggregationSelect(): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
QueryAggregationSelect.defaultProps = {
|
||||
onChange: undefined,
|
||||
};
|
||||
|
||||
export default QueryAggregationSelect;
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
IQueryContext,
|
||||
IValidationResult,
|
||||
} from 'types/antlrQueryTypes';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryKeySuggestionsProps } from 'types/api/querySuggestions/types';
|
||||
import { queryOperatorSuggestions, validateQuery } from 'utils/antlrQueryUtils';
|
||||
import { getQueryContextAtCursor } from 'utils/queryContextUtils';
|
||||
@ -61,9 +62,14 @@ const disallowMultipleSpaces: Extension = EditorView.inputHandler.of(
|
||||
},
|
||||
);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function QuerySearch(): JSX.Element {
|
||||
const [query, setQuery] = useState<string>('');
|
||||
function QuerySearch({
|
||||
onChange,
|
||||
queryData,
|
||||
}: {
|
||||
onChange: (value: string) => void;
|
||||
queryData: IBuilderQuery;
|
||||
}): JSX.Element {
|
||||
const [query, setQuery] = useState<string>(queryData.filter?.expression || '');
|
||||
const [valueSuggestions, setValueSuggestions] = useState<any[]>([
|
||||
{ label: 'error', type: 'value' },
|
||||
{ label: 'frontend', type: 'value' },
|
||||
@ -343,6 +349,7 @@ function QuerySearch(): JSX.Element {
|
||||
const handleChange = (value: string): void => {
|
||||
setQuery(value);
|
||||
handleQueryChange(value);
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
const handleQueryValidation = (newQuery: string): void => {
|
||||
@ -366,6 +373,13 @@ function QuerySearch(): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (query) {
|
||||
handleQueryValidation(query);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleExampleClick = (exampleQuery: string): void => {
|
||||
// If there's an existing query, append the example with AND
|
||||
const newQuery = query ? `${query} AND ${exampleQuery}` : exampleQuery;
|
||||
|
||||
@ -10,6 +10,7 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { Copy, Ellipsis, Trash } from 'lucide-react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { HandleChangeQueryDataV5 } from 'types/common/operations.types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import MetricsAggregateSection from './MerticsAggregateSection/MetricsAggregateSection';
|
||||
@ -80,6 +81,26 @@ export const QueryV2 = memo(function QueryV2({
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
(value: string) => {
|
||||
(handleChangeQueryData as HandleChangeQueryDataV5)('filter', {
|
||||
expression: value,
|
||||
});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
const handleChangeAggregation = useCallback(
|
||||
(value: string) => {
|
||||
(handleChangeQueryData as HandleChangeQueryDataV5)('aggregations', [
|
||||
{
|
||||
expression: value,
|
||||
},
|
||||
]);
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx('query-v2', { 'where-clause-view': showOnlyWhereClause })}
|
||||
@ -154,7 +175,7 @@ export const QueryV2 = memo(function QueryV2({
|
||||
|
||||
<div className="qb-search-filter-container">
|
||||
<div className="query-search-container">
|
||||
<QuerySearch />
|
||||
<QuerySearch onChange={handleSearchChange} queryData={query} />
|
||||
</div>
|
||||
|
||||
{showSpanScopeSelector && (
|
||||
@ -166,13 +187,17 @@ export const QueryV2 = memo(function QueryV2({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!showOnlyWhereClause && !isListViewPanel && (
|
||||
<QueryAggregation
|
||||
dataSource={dataSource}
|
||||
panelType={panelType || undefined}
|
||||
onAggregationIntervalChange={handleChangeAggregateEvery}
|
||||
/>
|
||||
)}
|
||||
{!showOnlyWhereClause &&
|
||||
!isListViewPanel &&
|
||||
dataSource !== DataSource.METRICS && (
|
||||
<QueryAggregation
|
||||
dataSource={dataSource}
|
||||
panelType={panelType || undefined}
|
||||
onAggregationIntervalChange={handleChangeAggregateEvery}
|
||||
onChange={handleChangeAggregation}
|
||||
queryData={query}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!showOnlyWhereClause && dataSource === DataSource.METRICS && (
|
||||
<MetricsAggregateSection
|
||||
|
||||
@ -15,3 +15,4 @@ export const DASHBOARD_TIME_IN_DURATION = 'refreshInterval';
|
||||
|
||||
export const DEFAULT_ENTITY_VERSION = 'v3';
|
||||
export const ENTITY_VERSION_V4 = 'v4';
|
||||
export const ENTITY_VERSION_V5 = 'v5';
|
||||
|
||||
@ -2,7 +2,7 @@ import './ChartPreview.styles.scss';
|
||||
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@ -144,7 +144,8 @@ function ChartPreview({
|
||||
},
|
||||
originalGraphType: graphType,
|
||||
},
|
||||
alertDef?.version || DEFAULT_ENTITY_VERSION,
|
||||
// alertDef?.version || DEFAULT_ENTITY_VERSION,
|
||||
ENTITY_VERSION_V5,
|
||||
{
|
||||
queryKey: [
|
||||
'chartPreview',
|
||||
|
||||
@ -10,7 +10,7 @@ import cx from 'classnames';
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TimePreference from 'components/TimePreferenceDropDown';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import {
|
||||
@ -122,7 +122,8 @@ function FullView({
|
||||
|
||||
const response = useGetQueryRange(
|
||||
requestData,
|
||||
selectedDashboard?.data?.version || version || DEFAULT_ENTITY_VERSION,
|
||||
// selectedDashboard?.data?.version || version || DEFAULT_ENTITY_VERSION,
|
||||
ENTITY_VERSION_V5,
|
||||
{
|
||||
queryKey: [widget?.query, widget?.panelTypes, requestData, version],
|
||||
enabled: !isDependedDataLoaded,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
@ -209,7 +209,8 @@ function GridCardGraph({
|
||||
end: customTimeRange?.endTime || end,
|
||||
originalGraphType: widget?.panelTypes,
|
||||
},
|
||||
version || DEFAULT_ENTITY_VERSION,
|
||||
ENTITY_VERSION_V5,
|
||||
// version || DEFAULT_ENTITY_VERSION,
|
||||
{
|
||||
queryKey: [
|
||||
maxTime,
|
||||
|
||||
@ -6,7 +6,7 @@ import { getQueryStats, WsDataEvent } from 'api/common/getQueryStats';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { AVAILABLE_EXPORT_PANEL_TYPES } from 'constants/panelTypes';
|
||||
@ -248,7 +248,8 @@ function LogsExplorerViewsContainer({
|
||||
} = useGetExplorerQueryRange(
|
||||
listChartQuery,
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
ENTITY_VERSION_V4,
|
||||
// ENTITY_VERSION_V4,
|
||||
ENTITY_VERSION_V5,
|
||||
{
|
||||
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
|
||||
},
|
||||
@ -268,7 +269,8 @@ function LogsExplorerViewsContainer({
|
||||
} = useGetExplorerQueryRange(
|
||||
requestData,
|
||||
panelType,
|
||||
ENTITY_VERSION_V4,
|
||||
// ENTITY_VERSION_V4,
|
||||
ENTITY_VERSION_V5,
|
||||
{
|
||||
keepPreviousData: true,
|
||||
enabled: !isLimit && !!requestData,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters/BuilderUnitsFilter/BuilderUnits';
|
||||
@ -63,7 +63,7 @@ function TimeSeries({ showOneChartPerQuery }: TimeSeriesProps): JSX.Element {
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
payload,
|
||||
ENTITY_VERSION_V4,
|
||||
ENTITY_VERSION_V5,
|
||||
globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
@ -80,7 +80,8 @@ function TimeSeries({ showOneChartPerQuery }: TimeSeriesProps): JSX.Element {
|
||||
dataSource: DataSource.METRICS,
|
||||
},
|
||||
},
|
||||
ENTITY_VERSION_V4,
|
||||
// ENTITY_VERSION_V4,
|
||||
ENTITY_VERSION_V5,
|
||||
),
|
||||
enabled: !!payload,
|
||||
})),
|
||||
|
||||
@ -43,6 +43,7 @@ function WidgetGraphContainer({
|
||||
|
||||
if (
|
||||
selectedGraph !== PANEL_TYPES.LIST &&
|
||||
selectedGraph !== PANEL_TYPES.VALUE &&
|
||||
queryResponse.data?.payload.data?.result?.length === 0
|
||||
) {
|
||||
return (
|
||||
@ -52,7 +53,7 @@ function WidgetGraphContainer({
|
||||
);
|
||||
}
|
||||
if (
|
||||
selectedGraph === PANEL_TYPES.LIST &&
|
||||
(selectedGraph === PANEL_TYPES.LIST || selectedGraph === PANEL_TYPES.VALUE) &&
|
||||
queryResponse.data?.payload?.data?.newResult?.data?.result?.length === 0
|
||||
) {
|
||||
return (
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import './LeftContainer.styles.scss';
|
||||
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { memo, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@ -30,7 +29,7 @@ function LeftContainer({
|
||||
setQueryResponse,
|
||||
}: WidgetGraphProps): JSX.Element {
|
||||
const { stagedQuery } = useQueryBuilder();
|
||||
const { selectedDashboard } = useDashboard();
|
||||
// const { selectedDashboard } = useDashboard();
|
||||
|
||||
const { selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
@ -38,7 +37,8 @@ function LeftContainer({
|
||||
>((state) => state.globalTime);
|
||||
const queryResponse = useGetQueryRange(
|
||||
requestData,
|
||||
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
|
||||
// selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
|
||||
ENTITY_VERSION_V5,
|
||||
{
|
||||
enabled: !!stagedQuery,
|
||||
retry: false,
|
||||
|
||||
@ -9,9 +9,17 @@ function ValuePanelWrapper({
|
||||
}: PanelWrapperProps): JSX.Element {
|
||||
const { yAxisUnit, thresholds } = widget;
|
||||
const data = getUPlotChartData(queryResponse?.data?.payload);
|
||||
const dataNew = Object.values(
|
||||
queryResponse?.data?.payload?.data?.newResult?.data?.result[0]?.table
|
||||
?.rows?.[0]?.data || {},
|
||||
);
|
||||
|
||||
// this is for handling both query_range v3 and v5 responses
|
||||
const gridValueData = data?.[0].length > 0 ? data : [[0], dataNew];
|
||||
|
||||
return (
|
||||
<GridValueComponent
|
||||
data={data}
|
||||
data={gridValueData}
|
||||
yAxisUnit={yAxisUnit}
|
||||
thresholds={thresholds}
|
||||
/>
|
||||
|
||||
@ -5,13 +5,13 @@ export function removePrefix(str: string, type: string): string {
|
||||
const resourcePrefix = `${MetricsType.Resource}_`;
|
||||
const scopePrefix = `${MetricsType.Scope}_`;
|
||||
|
||||
if (str.startsWith(tagPrefix)) {
|
||||
if (str?.startsWith(tagPrefix)) {
|
||||
return str.slice(tagPrefix.length);
|
||||
}
|
||||
if (str.startsWith(resourcePrefix)) {
|
||||
if (str?.startsWith(resourcePrefix)) {
|
||||
return str.slice(resourcePrefix.length);
|
||||
}
|
||||
if (str.startsWith(scopePrefix) && type === MetricsType.Scope) {
|
||||
if (str?.startsWith(scopePrefix) && type === MetricsType.Scope) {
|
||||
return str.slice(scopePrefix.length);
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import './TimeSeriesView.styles.scss';
|
||||
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
@ -54,7 +54,8 @@ function TimeSeriesViewContainer({
|
||||
dataSource,
|
||||
},
|
||||
},
|
||||
ENTITY_VERSION_V4,
|
||||
// ENTITY_VERSION_V4,
|
||||
ENTITY_VERSION_V5,
|
||||
{
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
|
||||
@ -3,7 +3,7 @@ import './ListView.styles.scss';
|
||||
import { Select } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@ -110,7 +110,8 @@ function ListView({ isFilterApplied }: ListViewProps): JSX.Element {
|
||||
selectColumns: options?.selectColumns,
|
||||
},
|
||||
},
|
||||
ENTITY_VERSION_V4,
|
||||
// ENTITY_VERSION_V4,
|
||||
ENTITY_VERSION_V5,
|
||||
{
|
||||
queryKey,
|
||||
enabled:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Space } from 'antd';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { QueryTable } from 'container/QueryTable';
|
||||
@ -28,7 +28,8 @@ function TableView(): JSX.Element {
|
||||
dataSource: 'traces',
|
||||
},
|
||||
},
|
||||
ENTITY_VERSION_V4,
|
||||
// ENTITY_VERSION_V4,
|
||||
ENTITY_VERSION_V5,
|
||||
{
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Select, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
@ -68,7 +68,8 @@ function TracesView({ isFilterApplied }: TracesViewProps): JSX.Element {
|
||||
pagination: paginationQueryData,
|
||||
},
|
||||
},
|
||||
ENTITY_VERSION_V4,
|
||||
// ENTITY_VERSION_V4,
|
||||
ENTITY_VERSION_V5,
|
||||
{
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
|
||||
@ -34,6 +34,7 @@ import {
|
||||
import {
|
||||
HandleChangeFormulaData,
|
||||
HandleChangeQueryData,
|
||||
HandleChangeQueryDataV5,
|
||||
UseQueryOperations,
|
||||
} from 'types/common/operations.types';
|
||||
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||
@ -292,9 +293,11 @@ export const useQueryOperations: UseQueryOperations = ({
|
||||
index,
|
||||
]);
|
||||
|
||||
const handleChangeQueryData: HandleChangeQueryData = useCallback(
|
||||
(key, value) => {
|
||||
const newQuery: IBuilderQuery = {
|
||||
const handleChangeQueryData:
|
||||
| HandleChangeQueryData
|
||||
| HandleChangeQueryDataV5 = useCallback(
|
||||
(key: string, value: any) => {
|
||||
const newQuery = {
|
||||
...query,
|
||||
[key]:
|
||||
key === LEGEND && typeof value === 'string'
|
||||
|
||||
@ -3,6 +3,12 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { getMetricsQueryRange } from 'api/metrics/getQueryRange';
|
||||
import {
|
||||
convertV5ResponseToLegacy,
|
||||
getQueryRangeV5,
|
||||
prepareQueryRangePayloadV5,
|
||||
} from 'api/v5/v5';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||
@ -26,13 +32,42 @@ export async function GetMetricQueryRange(
|
||||
headers?: Record<string, string>,
|
||||
isInfraMonitoring?: boolean,
|
||||
): Promise<SuccessResponse<MetricRangePayloadProps>> {
|
||||
const { legendMap, queryPayload } = prepareQueryRangePayload(props);
|
||||
const response = await getMetricsQueryRange(
|
||||
queryPayload,
|
||||
version || 'v3',
|
||||
signal,
|
||||
headers,
|
||||
);
|
||||
let legendMap: Record<string, string>;
|
||||
let response: SuccessResponse<MetricRangePayloadProps>;
|
||||
|
||||
if (version === ENTITY_VERSION_V5) {
|
||||
const v5Result = prepareQueryRangePayloadV5(props);
|
||||
legendMap = v5Result.legendMap;
|
||||
|
||||
const v5Response = await getQueryRangeV5(
|
||||
v5Result.queryPayload,
|
||||
version,
|
||||
signal,
|
||||
headers,
|
||||
);
|
||||
|
||||
// Convert V5 response to legacy format for components
|
||||
response = convertV5ResponseToLegacy(v5Response, legendMap);
|
||||
} else {
|
||||
const legacyResult = prepareQueryRangePayload(props);
|
||||
legendMap = legacyResult.legendMap;
|
||||
|
||||
response = await getMetricsQueryRange(
|
||||
legacyResult.queryPayload,
|
||||
version || 'v3',
|
||||
signal,
|
||||
headers,
|
||||
);
|
||||
}
|
||||
|
||||
// todo: Sagar
|
||||
if (response.statusCode >= 400 && version === ENTITY_VERSION_V5) {
|
||||
let error = `API responded with ${response.statusCode} - ${response.error?.message} status: ${response?.status}`;
|
||||
if (response.body && !isEmpty(response.body)) {
|
||||
error = `${error}, errors: ${response.body}`;
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
let error = `API responded with ${response.statusCode} - ${response.error} status: ${response.message}`;
|
||||
|
||||
@ -6,6 +6,13 @@ import {
|
||||
ReduceOperators,
|
||||
} from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
Filter,
|
||||
Having as HavingV5,
|
||||
LogAggregation,
|
||||
MetricAggregation,
|
||||
TraceAggregation,
|
||||
} from '../v5/queryRange';
|
||||
import { BaseAutocompleteData } from './queryAutocompleteResponse';
|
||||
|
||||
// Type for Formula
|
||||
@ -59,15 +66,18 @@ export type IBuilderQuery = {
|
||||
dataSource: DataSource;
|
||||
aggregateOperator: string;
|
||||
aggregateAttribute: BaseAutocompleteData;
|
||||
aggregations?: TraceAggregation[] | LogAggregation[] | MetricAggregation[];
|
||||
timeAggregation: string;
|
||||
spaceAggregation?: string;
|
||||
temporality?: string;
|
||||
functions: QueryFunctionProps[];
|
||||
filter?: Filter;
|
||||
filters: TagFilter;
|
||||
groupBy: BaseAutocompleteData[];
|
||||
expression: string;
|
||||
disabled: boolean;
|
||||
having: Having[];
|
||||
havingExpression?: HavingV5;
|
||||
limit: number | null;
|
||||
stepInterval: number;
|
||||
orderBy: OrderByPayload[];
|
||||
|
||||
397
frontend/src/types/api/v5/queryRange.ts
Normal file
397
frontend/src/types/api/v5/queryRange.ts
Normal file
@ -0,0 +1,397 @@
|
||||
// ===================== Base Types =====================
|
||||
|
||||
export type Step = string | number; // Duration string (e.g., "30s") or seconds as number
|
||||
|
||||
export type RequestType =
|
||||
| 'scalar'
|
||||
| 'time_series'
|
||||
| 'raw'
|
||||
| 'distribution'
|
||||
| '';
|
||||
|
||||
export type QueryType =
|
||||
| 'builder_query'
|
||||
| 'builder_formula'
|
||||
| 'builder_sub_query'
|
||||
| 'builder_join'
|
||||
| 'clickhouse_sql'
|
||||
| 'promql';
|
||||
|
||||
export type OrderDirection = 'asc' | 'desc';
|
||||
|
||||
export type JoinType = 'inner' | 'left' | 'right' | 'full' | 'cross';
|
||||
|
||||
export type SignalType = 'traces' | 'logs' | 'metrics';
|
||||
|
||||
export type DataType = 'string' | 'number' | 'boolean' | 'array';
|
||||
|
||||
export type FieldType =
|
||||
| 'resource'
|
||||
| 'attribute'
|
||||
| 'instrumentation_library'
|
||||
| 'span';
|
||||
|
||||
export type FieldContext =
|
||||
| 'metric'
|
||||
| 'log'
|
||||
| 'span'
|
||||
| 'trace'
|
||||
| 'resource'
|
||||
| 'scope'
|
||||
| 'attribute'
|
||||
| 'event'
|
||||
| '';
|
||||
|
||||
export type FieldDataType =
|
||||
| 'string'
|
||||
| 'bool'
|
||||
| 'float64'
|
||||
| 'int64'
|
||||
| 'number'
|
||||
| '[]string'
|
||||
| '[]float64'
|
||||
| '[]bool'
|
||||
| '[]int64'
|
||||
| '[]number'
|
||||
| '';
|
||||
|
||||
export type FunctionName =
|
||||
| 'cutOffMin'
|
||||
| 'cutOffMax'
|
||||
| 'clampMin'
|
||||
| 'clampMax'
|
||||
| 'absolute'
|
||||
| 'runningDiff'
|
||||
| 'log2'
|
||||
| 'log10'
|
||||
| 'cumSum'
|
||||
| 'ewma3'
|
||||
| 'ewma5'
|
||||
| 'ewma7'
|
||||
| 'median3'
|
||||
| 'median5'
|
||||
| 'median7'
|
||||
| 'timeShift'
|
||||
| 'anomaly';
|
||||
|
||||
export type Temporality = 'cumulative' | 'delta' | '';
|
||||
|
||||
export type MetricType =
|
||||
| 'gauge'
|
||||
| 'sum'
|
||||
| 'histogram'
|
||||
| 'summary'
|
||||
| 'exponential_histogram'
|
||||
| '';
|
||||
|
||||
export type TimeAggregation =
|
||||
| 'latest'
|
||||
| 'sum'
|
||||
| 'avg'
|
||||
| 'min'
|
||||
| 'max'
|
||||
| 'count'
|
||||
| 'count_distinct'
|
||||
| 'rate'
|
||||
| 'increase'
|
||||
| '';
|
||||
|
||||
export type SpaceAggregation =
|
||||
| 'sum'
|
||||
| 'avg'
|
||||
| 'min'
|
||||
| 'max'
|
||||
| 'count'
|
||||
| 'p50'
|
||||
| 'p75'
|
||||
| 'p90'
|
||||
| 'p95'
|
||||
| 'p99'
|
||||
| '';
|
||||
|
||||
export type ColumnType = 'group' | 'aggregation';
|
||||
|
||||
// ===================== Core Interface Types =====================
|
||||
|
||||
export interface TelemetryFieldKey {
|
||||
name: string;
|
||||
description?: string;
|
||||
unit?: string;
|
||||
signal?: SignalType;
|
||||
fieldContext?: FieldContext;
|
||||
fieldDataType?: FieldDataType;
|
||||
materialized?: boolean;
|
||||
}
|
||||
|
||||
export interface Filter {
|
||||
expression: string;
|
||||
}
|
||||
|
||||
export interface Having {
|
||||
expression: string;
|
||||
}
|
||||
|
||||
export type GroupByKey = TelemetryFieldKey;
|
||||
|
||||
export interface OrderBy {
|
||||
key: TelemetryFieldKey;
|
||||
direction: OrderDirection;
|
||||
}
|
||||
|
||||
export interface LimitBy {
|
||||
keys: string[];
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface QueryRef {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface FunctionArg {
|
||||
name?: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export interface QueryFunction {
|
||||
name: FunctionName;
|
||||
args?: FunctionArg[];
|
||||
}
|
||||
|
||||
// ===================== Aggregation Types =====================
|
||||
|
||||
export interface TraceAggregation {
|
||||
expression: string;
|
||||
alias?: string;
|
||||
}
|
||||
|
||||
export interface LogAggregation {
|
||||
expression: string;
|
||||
alias?: string;
|
||||
}
|
||||
|
||||
export interface MetricAggregation {
|
||||
metricName: string;
|
||||
temporality: Temporality;
|
||||
timeAggregation: TimeAggregation;
|
||||
spaceAggregation: SpaceAggregation;
|
||||
}
|
||||
|
||||
export interface SecondaryAggregation {
|
||||
stepInterval?: Step;
|
||||
expression: string;
|
||||
alias?: string;
|
||||
groupBy?: GroupByKey[];
|
||||
order?: OrderBy[];
|
||||
limit?: number;
|
||||
limitBy?: LimitBy;
|
||||
}
|
||||
|
||||
// ===================== Query Types =====================
|
||||
|
||||
export interface BaseBuilderQuery {
|
||||
name?: string;
|
||||
stepInterval?: Step;
|
||||
disabled?: boolean;
|
||||
filter?: Filter;
|
||||
groupBy?: GroupByKey[];
|
||||
order?: OrderBy[];
|
||||
selectFields?: TelemetryFieldKey[];
|
||||
limit?: number;
|
||||
limitBy?: LimitBy;
|
||||
offset?: number;
|
||||
cursor?: string;
|
||||
having?: Having;
|
||||
secondaryAggregations?: SecondaryAggregation[];
|
||||
functions?: QueryFunction[];
|
||||
legend?: string;
|
||||
}
|
||||
|
||||
export interface TraceBuilderQuery extends BaseBuilderQuery {
|
||||
signal: 'traces';
|
||||
aggregations?: TraceAggregation[];
|
||||
}
|
||||
|
||||
export interface LogBuilderQuery extends BaseBuilderQuery {
|
||||
signal: 'logs';
|
||||
aggregations?: LogAggregation[];
|
||||
}
|
||||
|
||||
export interface MetricBuilderQuery extends BaseBuilderQuery {
|
||||
signal: 'metrics';
|
||||
aggregations?: MetricAggregation[];
|
||||
}
|
||||
|
||||
export type BuilderQuery =
|
||||
| TraceBuilderQuery
|
||||
| LogBuilderQuery
|
||||
| MetricBuilderQuery;
|
||||
|
||||
export interface QueryBuilderFormula {
|
||||
name: string;
|
||||
expression: string;
|
||||
functions?: QueryFunction[];
|
||||
}
|
||||
|
||||
export interface QueryBuilderJoin {
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
left: QueryRef;
|
||||
right: QueryRef;
|
||||
type: JoinType;
|
||||
on: string;
|
||||
aggregations?: any[]; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
selectFields?: TelemetryFieldKey[];
|
||||
filter?: Filter;
|
||||
groupBy?: GroupByKey[];
|
||||
having?: Having;
|
||||
order?: OrderBy[];
|
||||
limit?: number;
|
||||
secondaryAggregations?: SecondaryAggregation[];
|
||||
functions?: QueryFunction[];
|
||||
}
|
||||
|
||||
export interface PromQuery {
|
||||
name: string;
|
||||
query: string;
|
||||
disabled?: boolean;
|
||||
stats?: boolean;
|
||||
step?: Step;
|
||||
}
|
||||
|
||||
export interface ClickHouseQuery {
|
||||
name: string;
|
||||
query: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// ===================== Query Envelope =====================
|
||||
|
||||
export interface QueryEnvelope {
|
||||
type: QueryType;
|
||||
spec:
|
||||
| BuilderQuery // Will be same for both builder_query and builder_sub_query
|
||||
| QueryBuilderFormula
|
||||
| QueryBuilderJoin
|
||||
| PromQuery
|
||||
| ClickHouseQuery;
|
||||
}
|
||||
|
||||
export interface CompositeQuery {
|
||||
queries: QueryEnvelope[];
|
||||
}
|
||||
|
||||
// ===================== Request Types =====================
|
||||
|
||||
export interface QueryRangeRequestV5 {
|
||||
schemaVersion: string;
|
||||
start: number; // epoch milliseconds
|
||||
end: number; // epoch milliseconds
|
||||
requestType: RequestType;
|
||||
compositeQuery: CompositeQuery;
|
||||
variables?: Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
// ===================== Response Types =====================
|
||||
|
||||
export interface ExecStats {
|
||||
rowsScanned: number;
|
||||
bytesScanned: number;
|
||||
durationMs: number;
|
||||
}
|
||||
|
||||
export interface Label {
|
||||
key: TelemetryFieldKey;
|
||||
value: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
export interface Bucket {
|
||||
step: number;
|
||||
}
|
||||
|
||||
export interface TimeSeriesValue {
|
||||
timestamp: number; // Unix timestamp in milliseconds
|
||||
value: number;
|
||||
values?: number[]; // For heatmap type charts
|
||||
bucket?: Bucket;
|
||||
}
|
||||
|
||||
export interface TimeSeries {
|
||||
labels?: Label[];
|
||||
values: TimeSeriesValue[];
|
||||
}
|
||||
|
||||
export interface AggregationBucket {
|
||||
index: number;
|
||||
alias: string;
|
||||
meta: Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
series: TimeSeries[];
|
||||
}
|
||||
|
||||
export interface TimeSeriesData {
|
||||
queryName: string;
|
||||
aggregations: AggregationBucket[];
|
||||
}
|
||||
|
||||
export interface ColumnDescriptor extends TelemetryFieldKey {
|
||||
queryName: string;
|
||||
aggregationIndex: number;
|
||||
columnType: ColumnType;
|
||||
}
|
||||
|
||||
export interface ScalarData {
|
||||
columns: ColumnDescriptor[];
|
||||
data: any[][]; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
export interface RawRow {
|
||||
timestamp: string; // ISO date-time
|
||||
data: Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
export interface RawData {
|
||||
queryName: string;
|
||||
nextCursor?: string;
|
||||
rows: RawRow[];
|
||||
}
|
||||
|
||||
export interface DistributionData {
|
||||
// Structure to be defined based on requirements
|
||||
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
// Response data structures with results array
|
||||
export interface TimeSeriesResponseData {
|
||||
results: TimeSeriesData[];
|
||||
}
|
||||
|
||||
export interface ScalarResponseData {
|
||||
results: ScalarData[];
|
||||
}
|
||||
|
||||
export interface RawResponseData {
|
||||
results: RawData[];
|
||||
}
|
||||
|
||||
export interface DistributionResponseData {
|
||||
results: DistributionData[];
|
||||
}
|
||||
|
||||
export type QueryRangeDataV5 =
|
||||
| TimeSeriesResponseData
|
||||
| ScalarResponseData
|
||||
| RawResponseData
|
||||
| DistributionResponseData;
|
||||
|
||||
export interface QueryRangeResponseV5 {
|
||||
type: RequestType;
|
||||
data: QueryRangeDataV5;
|
||||
meta: ExecStats;
|
||||
}
|
||||
|
||||
// ===================== Payload Types for API Functions =====================
|
||||
|
||||
export type QueryRangePayloadV5 = QueryRangeRequestV5;
|
||||
|
||||
export interface MetricRangePayloadV5 {
|
||||
data: QueryRangeResponseV5;
|
||||
}
|
||||
@ -53,6 +53,18 @@ export interface QueryDataV3 {
|
||||
predictedSeries?: SeriesItem[] | null;
|
||||
anomalyScores?: SeriesItem[] | null;
|
||||
isAnomaly?: boolean;
|
||||
table?: {
|
||||
rows: {
|
||||
data: {
|
||||
[key: string]: any;
|
||||
};
|
||||
}[];
|
||||
columns: {
|
||||
name: string;
|
||||
queryName: string;
|
||||
isValueColumn: boolean;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
|
||||
@ -6,6 +6,12 @@ import {
|
||||
IBuilderQuery,
|
||||
QueryFunctionProps,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
BaseBuilderQuery,
|
||||
LogBuilderQuery,
|
||||
MetricBuilderQuery,
|
||||
TraceBuilderQuery,
|
||||
} from 'types/api/v5/queryRange';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { SelectOption } from './select';
|
||||
@ -17,14 +23,23 @@ type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'> &
|
||||
entityVersion: string;
|
||||
};
|
||||
|
||||
export type HandleChangeQueryData = <
|
||||
Key extends keyof IBuilderQuery,
|
||||
Value extends IBuilderQuery[Key]
|
||||
// Generic type that can work with both legacy and V5 query types
|
||||
export type HandleChangeQueryData<T = IBuilderQuery> = <
|
||||
Key extends keyof T,
|
||||
Value extends T[Key]
|
||||
>(
|
||||
key: Key,
|
||||
value: Value,
|
||||
) => void;
|
||||
|
||||
// Legacy version for backward compatibility
|
||||
export type HandleChangeQueryDataLegacy = HandleChangeQueryData<IBuilderQuery>;
|
||||
|
||||
// V5 version for new API
|
||||
export type HandleChangeQueryDataV5 = HandleChangeQueryData<
|
||||
BaseBuilderQuery & (TraceBuilderQuery | LogBuilderQuery | MetricBuilderQuery)
|
||||
>;
|
||||
|
||||
export type HandleChangeFormulaData = <
|
||||
Key extends keyof IBuilderFormula,
|
||||
Value extends IBuilderFormula[Key]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user