diff --git a/frontend/src/api/v5/queryRange/convertV5Response.ts b/frontend/src/api/v5/queryRange/convertV5Response.ts index 6d020cbcf7c7..f7d3fea241b1 100644 --- a/frontend/src/api/v5/queryRange/convertV5Response.ts +++ b/frontend/src/api/v5/queryRange/convertV5Response.ts @@ -41,174 +41,55 @@ function convertTimeSeriesData( }; } -/** - * 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 }[] { - // First, identify all group columns and all value columns - const allGroupColumns = new Set(); - const allValueColumns = new Set(); - - 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>(); - - // 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 = {}; - - 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 = { ...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, -): QueryDataV3 { +): QueryDataV3[] { // If no scalar data, return empty structure + if (!scalarDataArray || scalarDataArray.length === 0) { + return []; + } + + // Process each scalar data separately to maintain query separation + return scalarDataArray?.map((scalarData) => { + // Get query name from the first column + const queryName = scalarData?.columns?.[0]?.queryName || ''; + + // Collect columns for this specific query + const columns = scalarData?.columns?.map((col) => ({ + name: col.columnType === 'aggregation' ? col.queryName : col.name, + queryName: col.queryName, + isValueColumn: col.columnType === 'aggregation', + })); + + // Process rows for this specific query + const rows = scalarData?.data?.map((dataRow) => { + const rowData: Record = {}; + + scalarData?.columns?.forEach((col, colIndex) => { + const columnName = + col.columnType === 'aggregation' ? col.queryName : col.name; + rowData[columnName] = dataRow[colIndex]; + }); + + return { data: rowData }; + }); + return { - queryName: '', - legend: '', + queryName, + legend: legendMap[queryName] || '', series: null, list: null, table: { - columns: [], - rows: [], + 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, - }, - }; + }); } /** @@ -268,11 +149,11 @@ function convertV5DataByType( } case 'scalar': { const scalarData = v5Data.data.results as ScalarData[]; - // For scalar data, combine all results into a single table - const combinedTable = convertScalarDataArrayToTable(scalarData, legendMap); + // For scalar data, combine all results into separate table entries + const combinedTables = convertScalarDataArrayToTable(scalarData, legendMap); return { resultType: 'scalar', - result: [combinedTable], + result: combinedTables, }; } case 'raw': { @@ -339,7 +220,7 @@ export function convertV5ResponseToLegacy( // 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) { + if (newQueryData.legend === undefined || newQueryData.legend === null) { newQueryData.legend = queryData.queryName; } // If name of the query and the legend if inserted is same then add the same to the metrics object. diff --git a/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts b/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts index 02e7da89f2c0..4c9b2b31d074 100644 --- a/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts +++ b/frontend/src/api/v5/queryRange/prepareQueryRangePayloadV5.ts @@ -25,6 +25,7 @@ import { RequestType, TelemetryFieldKey, TraceAggregation, + VariableItem, } from 'types/api/v5/queryRange'; import { EQueryType } from 'types/common/dashboard'; import { DataSource } from 'types/common/queryBuilder'; @@ -316,13 +317,12 @@ export const prepareQueryRangePayloadV5 = ({ variables = {}, start: startTime, end: endTime, + formatForWeb, }: GetQueryResultsProps): PrepareQueryRangePayloadV5Result => { let legendMap: Record = {}; const requestType = mapPanelTypeToRequestType(graphType); let queries: QueryEnvelope[] = []; - console.log('query', query); - switch (query.queryType) { case EQueryType.QUERY_BUILDER: { const { queryData: data, queryFormulas } = query.builder; @@ -380,7 +380,13 @@ export const prepareQueryRangePayloadV5 = ({ compositeQuery: { queries, }, - variables, + formatOptions: { + formatTableResultForUI: !!formatForWeb, + }, + variables: Object.entries(variables).reduce((acc, [key, value]) => { + acc[key] = { value }; + return acc; + }, {} as Record), }; return { legendMap, queryPayload }; diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/HavingFilter/HavingFilter.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/HavingFilter/HavingFilter.tsx index 357d97d1358b..77de264e9f0b 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/HavingFilter/HavingFilter.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAddOns/HavingFilter/HavingFilter.tsx @@ -69,6 +69,30 @@ const conjunctions = [ { label: 'OR', value: 'OR ' }, ]; +// Custom extension to stop events from propagating to global shortcuts +const stopEventsExtension = EditorView.domEventHandlers({ + keydown: (event) => { + // Stop all keyboard events from propagating to global shortcuts + event.stopPropagation(); + event.stopImmediatePropagation(); + return false; // Important for CM to know you handled it + }, + input: (event) => { + event.stopPropagation(); + return false; + }, + focus: (event) => { + // Ensure focus events don't interfere with global shortcuts + event.stopPropagation(); + return false; + }, + blur: (event) => { + // Ensure blur events don't interfere with global shortcuts + event.stopPropagation(); + return false; + }, +}); + function HavingFilter({ onClose, onChange, @@ -82,6 +106,11 @@ function HavingFilter({ const [input, setInput] = useState( queryData?.havingExpression?.expression || '', ); + + useEffect(() => { + setInput(queryData?.havingExpression?.expression || ''); + }, [queryData?.havingExpression?.expression]); + const [isFocused, setIsFocused] = useState(false); const editorRef = useRef(null); @@ -316,6 +345,7 @@ function HavingFilter({ extensions={[ havingAutocomplete, javascript({ jsx: false, typescript: false }), + stopEventsExtension, keymap.of([ ...completionKeymap, { diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx index 2e150c338600..d4b16a88a2d2 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.tsx @@ -48,10 +48,12 @@ function QueryAggregationOptions({
every
{ + // Stop all keyboard events from propagating to global shortcuts + event.stopPropagation(); + event.stopImmediatePropagation(); + return false; // Important for CM to know you handled it + }, + input: (event) => { + event.stopPropagation(); + return false; + }, + focus: (event) => { + // Ensure focus events don't interfere with global shortcuts + event.stopPropagation(); + return false; + }, + blur: (event) => { + // Ensure blur events don't interfere with global shortcuts + event.stopPropagation(); + return false; + }, +}); + // eslint-disable-next-line react/no-this-in-sfc function QueryAggregationSelect({ onChange, @@ -125,6 +149,13 @@ function QueryAggregationSelect({ const [input, setInput] = useState( queryData?.aggregations?.map((i: any) => i.expression).join(' ') || '', ); + + useEffect(() => { + setInput( + queryData?.aggregations?.map((i: any) => i.expression).join(' ') || '', + ); + }, [queryData?.aggregations]); + const [cursorPos, setCursorPos] = useState(0); const [functionArgPairs, setFunctionArgPairs] = useState< { func: string; arg: string }[] @@ -457,6 +488,7 @@ function QueryAggregationSelect({ aggregatorAutocomplete, javascript({ jsx: false, typescript: false }), EditorView.lineWrapping, + stopEventsExtension, keymap.of([ ...completionKeymap, { diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx index 97eb3dd9b2d0..b30ba6de4789 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx @@ -41,14 +41,25 @@ const { Panel } = Collapse; // Custom extension to stop events const stopEventsExtension = EditorView.domEventHandlers({ keydown: (event) => { + // Stop all keyboard events from propagating to global shortcuts event.stopPropagation(); - // Optionally: event.preventDefault(); + event.stopImmediatePropagation(); return false; // Important for CM to know you handled it }, input: (event) => { event.stopPropagation(); return false; }, + focus: (event) => { + // Ensure focus events don't interfere with global shortcuts + event.stopPropagation(); + return false; + }, + blur: (event) => { + // Ensure blur events don't interfere with global shortcuts + event.stopPropagation(); + return false; + }, }); const disallowMultipleSpaces: Extension = EditorView.inputHandler.of( @@ -86,6 +97,10 @@ function QuerySearch({ errors: [], }); + useEffect(() => { + setQuery(queryData.filter?.expression || ''); + }, [queryData.filter?.expression]); + const [keySuggestions, setKeySuggestions] = useState< QueryKeyDataSuggestionsProps[] | null >(null); diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx index 9372a616c888..ad3a4fe7276c 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QueryV2.tsx @@ -8,16 +8,11 @@ import SpanScopeSelector from 'container/QueryBuilder/filters/QueryBuilderSearch import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; import { Copy, Ellipsis, Trash } from 'lucide-react'; -import { memo, useCallback, useEffect, useMemo, useState } from '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 { - convertAggregationToExpression, - convertFiltersToExpression, - convertHavingToExpression, -} from '../utils'; import MetricsAggregateSection from './MerticsAggregateSection/MetricsAggregateSection'; import { MetricsSelect } from './MetricsSelect/MetricsSelect'; import QueryAddOns from './QueryAddOns/QueryAddOns'; @@ -54,44 +49,6 @@ export const QueryV2 = memo(function QueryV2({ entityVersion: version, }); - // Convert old format to new format and update query when component mounts or query changes - const performQueryConversions = useCallback(() => { - // Convert filters if needed - if (query.filters?.items?.length > 0 && !query.filter?.expression) { - const convertedFilter = convertFiltersToExpression(query.filters); - handleChangeQueryData('filter', convertedFilter); - } - - // Convert having if needed - if (query.having?.length > 0 && !query.havingExpression?.expression) { - const convertedHaving = convertHavingToExpression(query.having); - handleChangeQueryData('havingExpression', convertedHaving); - } - - // Convert aggregation if needed - if (!query.aggregations && query.aggregateOperator) { - const convertedAggregation = convertAggregationToExpression( - query.aggregateOperator, - query.aggregateAttribute, - query.dataSource, - query.timeAggregation, - query.spaceAggregation, - ) as any; // Type assertion to handle union type - handleChangeQueryData('aggregations', convertedAggregation); - } - }, [query, handleChangeQueryData]); - - useEffect(() => { - const needsConversion = - (query.filters?.items?.length > 0 && !query.filter?.expression) || - (query.having?.length > 0 && !query.havingExpression?.expression) || - (!query.aggregations && query.aggregateOperator); - - if (needsConversion) { - performQueryConversions(); - } - }, [performQueryConversions, query]); - const handleToggleDisableQuery = useCallback(() => { handleChangeQueryData('disabled', !query.disabled); }, [handleChangeQueryData, query]); @@ -208,67 +165,69 @@ export const QueryV2 = memo(function QueryV2({
)} -
-
- {dataSource === DataSource.METRICS && ( -
- -
- )} - -
-
- -
- - {showSpanScopeSelector && ( -
-
in
- + {!isCollapsed && ( +
+
+ {dataSource === DataSource.METRICS && ( +
+
)} -
-
- {!showOnlyWhereClause && - !isListViewPanel && - dataSource !== DataSource.METRICS && ( - +
+ +
+ + {showSpanScopeSelector && ( +
+
in
+ +
+ )} +
+
+ + {!showOnlyWhereClause && + !isListViewPanel && + dataSource !== DataSource.METRICS && ( + + )} + + {!showOnlyWhereClause && dataSource === DataSource.METRICS && ( + )} - {!showOnlyWhereClause && dataSource === DataSource.METRICS && ( - - )} - - {!showOnlyWhereClause && ( - - )} -
+ {!showOnlyWhereClause && ( + + )} +
+ )} ); diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 2ddc210289a7..525d4ea61d77 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -169,6 +169,9 @@ export const initialQueryBuilderFormValues: IBuilderQuery = { aggregateAttribute: initialAutocompleteData, timeAggregation: MetricAggregateOperator.RATE, spaceAggregation: MetricAggregateOperator.SUM, + filter: { expression: '' }, + aggregations: [{ expression: 'count() ' }], + havingExpression: { expression: '' }, functions: [], filters: { items: [], op: 'AND' }, expression: createNewBuilderItemName({ diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx index 0b259d7650d8..1ad2373c4dff 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx @@ -54,19 +54,21 @@ function DomainList(): JSX.Element { // initialise tab with default query. useShareBuilderUrl({ - ...initialQueriesMap.traces, - builder: { - ...initialQueriesMap.traces.builder, - queryData: [ - { - ...initialQueriesMap.traces.builder.queryData[0], - dataSource: DataSource.TRACES, - aggregateOperator: 'noop', - aggregateAttribute: { - ...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute, + defaultValue: { + ...initialQueriesMap.traces, + builder: { + ...initialQueriesMap.traces.builder, + queryData: [ + { + ...initialQueriesMap.traces.builder.queryData[0], + dataSource: DataSource.TRACES, + aggregateOperator: 'noop', + aggregateAttribute: { + ...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute, + }, }, - }, - ], + ], + }, }, }); diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index 56ebc193f46c..4f615f2c145e 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -8,6 +8,7 @@ import { QueryParams } from 'constants/query'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import AnomalyAlertEvaluationView from 'container/AnomalyAlertEvaluationView'; import GridPanelSwitch from 'container/GridPanelSwitch'; +import { populateMultipleResults } from 'container/NewWidget/LeftContainer/WidgetGraph/util'; import { getFormatNameByOptionId } from 'container/NewWidget/RightContainer/alertFomatCategories'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; import { Time } from 'container/TopNav/DateTimeSelection/config'; @@ -176,6 +177,12 @@ function ChartPreview({ queryResponse.data.payload.data.result = sortedSeriesData; } + if (queryResponse.data && graphType === PANEL_TYPES.PIE) { + const transformedData = populateMultipleResults(queryResponse?.data); + // eslint-disable-next-line no-param-reassign + queryResponse.data = transformedData; + } + const containerDimensions = useResizeObserver(graphRef); const isDarkMode = useIsDarkMode(); diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index 6a56beb05830..296525dbde97 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -162,7 +162,7 @@ function FormAlertRules({ const sq = useMemo(() => mapQueryDataFromApi(initQuery), [initQuery]); - useShareBuilderUrl(sq); + useShareBuilderUrl({ defaultValue: sq }); const handleDetectionMethodChange = (value: string): void => { setAlertDef((def) => ({ @@ -832,7 +832,7 @@ function FormAlertRules({ queryCategory={currentQuery.queryType} setQueryCategory={onQueryCategoryChange} alertType={alertType || AlertTypes.METRICS_BASED_ALERT} - runQuery={(): void => handleRunQuery(true)} + runQuery={(): void => handleRunQuery(true, true)} alertDef={alertDef} panelType={panelType || PANEL_TYPES.TIME_SERIES} key={currentQuery.queryType} diff --git a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx index 015e8de37fcc..b06e4f5842bb 100644 --- a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx @@ -13,6 +13,7 @@ import TimePreference from 'components/TimePreferenceDropDown'; import { ENTITY_VERSION_V5 } from 'constants/app'; import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; +import { populateMultipleResults } from 'container/NewWidget/LeftContainer/WidgetGraph/util'; import { timeItems, timePreferance, @@ -179,6 +180,12 @@ function FullView({ response.data.payload.data.result = sortedSeriesData; } + if (response.data && widget.panelTypes === PANEL_TYPES.PIE) { + const transformedData = populateMultipleResults(response?.data); + // eslint-disable-next-line no-param-reassign + response.data = transformedData; + } + useEffect(() => { graphsVisibilityStates?.forEach((e, i) => { fullViewChartRef?.current?.toggleGraph(i, e); diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index 5c8761ba5e71..8af579adb1ea 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -1,7 +1,8 @@ import logEvent from 'api/common/logEvent'; -import { ENTITY_VERSION_V5 } from 'constants/app'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; +import { populateMultipleResults } from 'container/NewWidget/LeftContainer/WidgetGraph/util'; import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useIntersectionObserver } from 'hooks/useIntersectionObserver'; @@ -209,8 +210,7 @@ function GridCardGraph({ end: customTimeRange?.endTime || end, originalGraphType: widget?.panelTypes, }, - ENTITY_VERSION_V5, - // version || DEFAULT_ENTITY_VERSION, + version || DEFAULT_ENTITY_VERSION, { queryKey: [ maxTime, @@ -272,6 +272,12 @@ function GridCardGraph({ queryResponse.data.payload.data.result = sortedSeriesData; } + if (queryResponse.data && widget.panelTypes === PANEL_TYPES.PIE) { + const transformedData = populateMultipleResults(queryResponse?.data); + // eslint-disable-next-line no-param-reassign + queryResponse.data = transformedData; + } + const menuList = widget.panelTypes === PANEL_TYPES.TABLE || widget.panelTypes === PANEL_TYPES.LIST || diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.tsx b/frontend/src/container/GridCardLayout/GridCardLayout.tsx index c42bbda45007..ee2d573935d0 100644 --- a/frontend/src/container/GridCardLayout/GridCardLayout.tsx +++ b/frontend/src/container/GridCardLayout/GridCardLayout.tsx @@ -6,6 +6,7 @@ import { Button, Form, Input, Modal, Typography } from 'antd'; import { useForm } from 'antd/es/form/Form'; import logEvent from 'api/common/logEvent'; import cx from 'classnames'; +import { ENTITY_VERSION_V5 } from 'constants/app'; import { QueryParams } from 'constants/query'; import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder'; import { themeColors } from 'constants/theme'; @@ -579,7 +580,8 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element { widget={(currentWidget as Widgets) || ({ id, query: {} } as Widgets)} headerMenuList={widgetActions} variables={variables} - version={selectedDashboard?.data?.version} + // version={selectedDashboard?.data?.version} + version={ENTITY_VERSION_V5} onDragSelect={onDragSelect} dataAvailable={checkIfDataExists} /> diff --git a/frontend/src/container/ListOfDashboard/DashboardsList.tsx b/frontend/src/container/ListOfDashboard/DashboardsList.tsx index d25f58cd2bb4..dd3f658b1618 100644 --- a/frontend/src/container/ListOfDashboard/DashboardsList.tsx +++ b/frontend/src/container/ListOfDashboard/DashboardsList.tsx @@ -25,7 +25,7 @@ import logEvent from 'api/common/logEvent'; import createDashboard from 'api/v1/dashboards/create'; import { AxiosError } from 'axios'; import cx from 'classnames'; -import { ENTITY_VERSION_V4 } from 'constants/app'; +import { ENTITY_VERSION_V5 } from 'constants/app'; import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats'; import ROUTES from 'constants/routes'; import { sanitizeDashboardData } from 'container/NewDashboard/DashboardDescription'; @@ -293,7 +293,7 @@ function DashboardsList(): JSX.Element { ns: 'dashboard', }), uploadedGrafana: false, - version: ENTITY_VERSION_V4, + version: ENTITY_VERSION_V5, }); safeNavigate( diff --git a/frontend/src/container/LogExplorerQuerySection/index.tsx b/frontend/src/container/LogExplorerQuerySection/index.tsx index a0247ac1e68f..76ecffafe298 100644 --- a/frontend/src/container/LogExplorerQuerySection/index.tsx +++ b/frontend/src/container/LogExplorerQuerySection/index.tsx @@ -34,7 +34,7 @@ function LogExplorerQuerySection({ [updateAllQueriesOperators], ); - useShareBuilderUrl(defaultValue); + useShareBuilderUrl({ defaultValue }); const filterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => { const isTable = panelTypes === PANEL_TYPES.TABLE; diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index 8792bdfc750b..69622ce2712b 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -341,15 +341,15 @@ function LogsExplorerViewsContainer({ query.builder.queryData.length > 1 ? query.builder.queryData.map((item) => ({ ...item, - ...(selectedPanelType !== PANEL_TYPES.LIST ? { order: [] } : {}), + ...(selectedView !== ExplorerViews.LIST ? { order: [] } : {}), })) : [ { ...(listQuery || initialQueryBuilderFormValues), ...paginateData, ...(updatedFilters ? { filters: updatedFilters } : {}), - ...(selectedPanelType === PANEL_TYPES.LIST - ? { order: orderBy } + ...(selectedView === ExplorerViews.LIST + ? { order: orderBy, orderBy } : { order: [] }), }, ]; @@ -364,7 +364,7 @@ function LogsExplorerViewsContainer({ return data; }, - [activeLogId, orderDirection, listQuery, selectedPanelType], + [activeLogId, orderDirection, listQuery, selectedView], ); const handleEndReached = useCallback(() => { diff --git a/frontend/src/container/MetricsExplorer/Explorer/Explorer.tsx b/frontend/src/container/MetricsExplorer/Explorer/Explorer.tsx index fa6042ded173..88530200d97e 100644 --- a/frontend/src/container/MetricsExplorer/Explorer/Explorer.tsx +++ b/frontend/src/container/MetricsExplorer/Explorer/Explorer.tsx @@ -75,7 +75,7 @@ function Explorer(): JSX.Element { [currentQuery, updateAllQueriesOperators], ); - useShareBuilderUrl(defaultQuery); + useShareBuilderUrl({ defaultValue: defaultQuery }); const handleExport = useCallback( ( @@ -132,7 +132,9 @@ function Explorer(): JSX.Element {
- + handleRunQuery(true, true)} + />
{/* */} diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx index fd38a238bd44..cebf1c206a83 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx @@ -64,7 +64,7 @@ function QuerySection({ const { query } = selectedWidget; - useShareBuilderUrl(query); + useShareBuilderUrl({ defaultValue: query }); const handleStageQuery = useCallback( (query: Query): void => { diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx index 72de2553ac12..b7abd49b171e 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx @@ -5,6 +5,7 @@ import { WidgetGraphContainerProps } from 'container/NewWidget/types'; import { getSortedSeriesData } from 'utils/getSortedSeriesData'; import { NotFoundContainer } from './styles'; +import { populateMultipleResults } from './util'; import WidgetGraph from './WidgetGraphs'; function WidgetGraphContainer({ @@ -22,6 +23,12 @@ function WidgetGraphContainer({ queryResponse.data.payload.data.result = sortedSeriesData; } + if (queryResponse.data && selectedGraph === PANEL_TYPES.PIE) { + const transformedData = populateMultipleResults(queryResponse?.data); + // eslint-disable-next-line no-param-reassign + queryResponse.data = transformedData; + } + if (selectedWidget === undefined) { return Invalid widget; } diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/util.ts b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/util.ts new file mode 100644 index 000000000000..09f6b9dfbb07 --- /dev/null +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/util.ts @@ -0,0 +1,51 @@ +import { SuccessResponse } from 'types/api'; +import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; +import { QueryData, QueryDataV3 } from 'types/api/widgets/getQuery'; + +export function populateMultipleResults( + responseData: SuccessResponse, +): SuccessResponse { + const queryResults = responseData?.payload?.data?.newResult?.data?.result; + const allFormattedResults: QueryData[] = []; + + queryResults?.forEach((query: QueryDataV3) => { + const { queryName, legend, table } = query; + if (!table) return; + + const { columns, rows } = table; + + const valueCol = columns?.find((c) => c.isValueColumn); + const labelCols = columns?.filter((c) => !c.isValueColumn); + + rows?.forEach((row) => { + const metric: Record = {}; + labelCols?.forEach((col) => { + metric[col.name] = String(row.data[col.name]); + }); + + allFormattedResults.push({ + metric, + values: [[0, String(row.data[valueCol!.name])]], + queryName, + legend: legend || '', + }); + }); + }); + + // Create a copy instead of mutating the original + const updatedResponseData: SuccessResponse< + MetricRangePayloadProps, + unknown + > = { + ...responseData, + payload: { + ...responseData.payload, + data: { + ...responseData.payload.data, + result: allFormattedResults, + }, + }, + }; + + return updatedResponseData; +} diff --git a/frontend/src/container/NewWidget/index.tsx b/frontend/src/container/NewWidget/index.tsx index fab2c2538cad..2cd36e9fe7a8 100644 --- a/frontend/src/container/NewWidget/index.tsx +++ b/frontend/src/container/NewWidget/index.tsx @@ -581,7 +581,11 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element { const setGraphHandler = (type: PANEL_TYPES): void => { setIsLoadingPanelData(true); - const updatedQuery = handleQueryChange(type as any, supersetQuery); + const updatedQuery = handleQueryChange( + type as any, + supersetQuery, + selectedGraph, + ); setGraphType(type); redirectWithQueryBuilderData( updatedQuery, diff --git a/frontend/src/container/NewWidget/utils.ts b/frontend/src/container/NewWidget/utils.ts index 81645d7e08d9..0c568645a626 100644 --- a/frontend/src/container/NewWidget/utils.ts +++ b/frontend/src/container/NewWidget/utils.ts @@ -56,9 +56,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -66,6 +68,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'legend', 'expression', + 'aggregations', ], }, }, @@ -76,10 +79,12 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateOperator', 'timeAggregation', 'filters', + 'filter', 'spaceAggregation', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'stepInterval', 'legend', @@ -87,6 +92,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'disabled', 'functions', 'expression', + 'aggregations', ], }, }, @@ -96,9 +102,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -106,6 +114,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'legend', 'expression', + 'aggregations', ], }, }, @@ -117,9 +126,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -127,6 +138,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'legend', 'expression', + 'aggregations', ], }, }, @@ -137,10 +149,12 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateOperator', 'timeAggregation', 'filters', + 'filter', 'spaceAggregation', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'stepInterval', 'legend', @@ -148,6 +162,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'disabled', 'functions', 'expression', + 'aggregations', ], }, }, @@ -157,9 +172,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -167,6 +184,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'legend', 'expression', + 'aggregations', ], }, }, @@ -178,9 +196,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -188,6 +208,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'legend', 'expression', + 'aggregations', ], }, }, @@ -198,10 +219,12 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateOperator', 'timeAggregation', 'filters', + 'filter', 'spaceAggregation', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'stepInterval', 'legend', @@ -209,6 +232,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'disabled', 'functions', 'expression', + 'aggregations', ], }, }, @@ -218,9 +242,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -228,6 +254,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'legend', 'expression', + 'aggregations', ], }, }, @@ -239,9 +266,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -249,6 +278,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'expression', 'legend', + 'aggregations', ], }, }, @@ -259,11 +289,13 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateOperator', 'timeAggregation', 'filters', + 'filter', 'spaceAggregation', 'groupBy', 'reduceTo', 'limit', 'having', + 'havingExpression', 'orderBy', 'stepInterval', 'legend', @@ -271,6 +303,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'expression', 'disabled', 'functions', + 'aggregations', ], }, }, @@ -280,9 +313,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -290,6 +325,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'expression', 'legend', + 'aggregations', ], }, }, @@ -301,9 +337,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -311,6 +349,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'expression', 'legend', + 'aggregations', ], }, }, @@ -321,11 +360,13 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateOperator', 'timeAggregation', 'filters', + 'filter', 'spaceAggregation', 'groupBy', 'reduceTo', 'limit', 'having', + 'havingExpression', 'orderBy', 'stepInterval', 'legend', @@ -333,6 +374,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'expression', 'disabled', 'functions', + 'aggregations', ], }, }, @@ -342,9 +384,11 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'groupBy', 'limit', 'having', + 'havingExpression', 'orderBy', 'functions', 'stepInterval', @@ -352,6 +396,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'queryName', 'expression', 'legend', + 'aggregations', ], }, }, @@ -359,17 +404,31 @@ export const panelTypeDataSourceFormValuesMap: Record< [PANEL_TYPES.LIST]: { [DataSource.LOGS]: { builder: { - queryData: ['filters', 'limit', 'orderBy', 'functions'], + queryData: [ + 'filters', + 'filter', + 'limit', + 'orderBy', + 'functions', + 'aggregations', + ], }, }, [DataSource.METRICS]: { builder: { - queryData: [], + queryData: ['filters', 'filter', 'aggregations'], }, }, [DataSource.TRACES]: { builder: { - queryData: ['filters', 'limit', 'orderBy', 'functions'], + queryData: [ + 'filters', + 'filter', + 'limit', + 'orderBy', + 'functions', + 'aggregations', + ], }, }, }, @@ -380,14 +439,17 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'reduceTo', 'having', + 'havingExpression', 'functions', 'stepInterval', 'queryName', 'expression', 'disabled', 'legend', + 'aggregations', ], }, }, @@ -398,8 +460,10 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateOperator', 'timeAggregation', 'filters', + 'filter', 'spaceAggregation', 'having', + 'havingExpression', 'reduceTo', 'stepInterval', 'legend', @@ -407,6 +471,7 @@ export const panelTypeDataSourceFormValuesMap: Record< 'expression', 'disabled', 'functions', + 'aggregations', ], }, }, @@ -416,14 +481,17 @@ export const panelTypeDataSourceFormValuesMap: Record< 'aggregateAttribute', 'aggregateOperator', 'filters', + 'filter', 'reduceTo', 'having', + 'havingExpression', 'functions', 'stepInterval', 'queryName', 'expression', 'disabled', 'legend', + 'aggregations', ], }, }, @@ -433,7 +501,9 @@ export const panelTypeDataSourceFormValuesMap: Record< export function handleQueryChange( newPanelType: keyof PartialPanelTypes, supersetQuery: Query, + currentPanelType: PANEL_TYPES, ): Query { + console.log('supersetQuery', supersetQuery); return { ...supersetQuery, builder: { @@ -454,6 +524,7 @@ export function handleQueryChange( set(tempQuery, 'aggregateOperator', 'noop'); set(tempQuery, 'offset', 0); set(tempQuery, 'pageSize', 10); + set(tempQuery, 'orderBy', undefined); } else if (tempQuery.aggregateOperator === 'noop') { // this condition takes care of the part where we start with the list panel type and then shift to other panels // because in other cases we never set list operator and other fields in superset query rather just update in the current / staged query @@ -462,6 +533,13 @@ export function handleQueryChange( unset(tempQuery, 'pageSize'); } + if ( + currentPanelType === PANEL_TYPES.LIST && + newPanelType !== PANEL_TYPES.LIST + ) { + set(tempQuery, 'orderBy', undefined); + } + return tempQuery; }), }, diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx index f82012223f3e..01583a1ecaee 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.tsx @@ -450,7 +450,7 @@ function QueryBuilderSearchV2( if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') { event.preventDefault(); event.stopPropagation(); - handleRunQuery(); + handleRunQuery(false, true); setIsOpen(false); } }, diff --git a/frontend/src/container/ResourceAttributeFilterV2/ResourceAttributesFilterV2.tsx b/frontend/src/container/ResourceAttributeFilterV2/ResourceAttributesFilterV2.tsx index 3a0be5d24ef3..79bbc30c1305 100644 --- a/frontend/src/container/ResourceAttributeFilterV2/ResourceAttributesFilterV2.tsx +++ b/frontend/src/container/ResourceAttributeFilterV2/ResourceAttributesFilterV2.tsx @@ -21,21 +21,23 @@ function ResourceAttributesFilter(): JSX.Element | null { // initialise tab with default query. useShareBuilderUrl({ - ...initialQueriesMap.traces, - builder: { - ...initialQueriesMap.traces.builder, - queryData: [ - { - ...initialQueriesMap.traces.builder.queryData[0], - dataSource: DataSource.TRACES, - aggregateOperator: 'noop', - aggregateAttribute: { - ...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute, - type: 'resource', + defaultValue: { + ...initialQueriesMap.traces, + builder: { + ...initialQueriesMap.traces.builder, + queryData: [ + { + ...initialQueriesMap.traces.builder.queryData[0], + dataSource: DataSource.TRACES, + aggregateOperator: 'noop', + aggregateAttribute: { + ...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute, + type: 'resource', + }, + queryName: '', }, - queryName: '', - }, - ], + ], + }, }, }); diff --git a/frontend/src/hooks/hotkeys/useKeyboardHotkeys.tsx b/frontend/src/hooks/hotkeys/useKeyboardHotkeys.tsx index 12b70fa68e07..311d6b88b526 100644 --- a/frontend/src/hooks/hotkeys/useKeyboardHotkeys.tsx +++ b/frontend/src/hooks/hotkeys/useKeyboardHotkeys.tsx @@ -31,7 +31,7 @@ const KeyboardHotkeysContext = createContext( }, ); -const IGNORE_INPUTS = ['input', 'textarea']; // Inputs in which hotkey events will be ignored +const IGNORE_INPUTS = ['input', 'textarea', 'cm-editor']; // Inputs in which hotkey events will be ignored const useKeyboardHotkeys = (): KeyboardHotkeysContextReturnValue => { const context = useContext(KeyboardHotkeysContext); @@ -54,7 +54,13 @@ function KeyboardHotkeysProvider({ const handleKeyPress = (event: KeyboardEvent): void => { const { key, ctrlKey, altKey, shiftKey, metaKey, target } = event; - if (IGNORE_INPUTS.includes((target as HTMLElement).tagName.toLowerCase())) { + const isCodeMirrorEditor = + (target as HTMLElement).closest('.cm-editor') !== null; + + if ( + IGNORE_INPUTS.includes((target as HTMLElement).tagName.toLowerCase()) || + isCodeMirrorEditor + ) { return; } diff --git a/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts b/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts index efef00022a0f..0f434a7b4051 100644 --- a/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts +++ b/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts @@ -1,3 +1,8 @@ +import { + convertAggregationToExpression, + convertFiltersToExpression, + convertHavingToExpression, +} from 'components/QueryBuilderV2/utils'; import { QueryParams } from 'constants/query'; import useUrlQuery from 'hooks/useUrlQuery'; import { useMemo } from 'react'; @@ -18,6 +23,41 @@ export const useGetCompositeQueryParam = (): Query | null => { parsedCompositeQuery = JSON.parse( decodeURIComponent(compositeQuery.replace(/\+/g, ' ')), ); + + // Convert old format to new format for each query in builder.queryData + if (parsedCompositeQuery?.builder?.queryData) { + parsedCompositeQuery.builder.queryData = parsedCompositeQuery.builder.queryData.map( + (query) => { + const convertedQuery = { ...query }; + + // Convert filters if needed + if (query.filters?.items?.length > 0 && !query.filter?.expression) { + const convertedFilter = convertFiltersToExpression(query.filters); + convertedQuery.filter = convertedFilter; + } + + // Convert having if needed + if (query.having?.length > 0 && !query.havingExpression?.expression) { + const convertedHaving = convertHavingToExpression(query.having); + convertedQuery.havingExpression = convertedHaving; + } + + // Convert aggregation if needed + if (!query.aggregations && query.aggregateOperator) { + const convertedAggregation = convertAggregationToExpression( + query.aggregateOperator, + query.aggregateAttribute, + query.dataSource, + query.timeAggregation, + query.spaceAggregation, + ) as any; // Type assertion to handle union type + convertedQuery.aggregations = convertedAggregation; + } + + return convertedQuery; + }, + ); + } } catch (e) { parsedCompositeQuery = null; } diff --git a/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts b/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts index c79da91b3bb6..7fcda4d37269 100644 --- a/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts +++ b/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts @@ -5,24 +5,32 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { useGetCompositeQueryParam } from './useGetCompositeQueryParam'; import { useQueryBuilder } from './useQueryBuilder'; -export type UseShareBuilderUrlParams = { defaultValue: Query }; +export type UseShareBuilderUrlParams = { + defaultValue: Query; + /** Force reset the query regardless of URL state */ + forceReset?: boolean; +}; -export const useShareBuilderUrl = (defaultQuery: Query): void => { +export const useShareBuilderUrl = ({ + defaultValue, + forceReset = false, +}: UseShareBuilderUrlParams): void => { const { resetQuery, redirectWithQueryBuilderData } = useQueryBuilder(); const urlQuery = useUrlQuery(); const compositeQuery = useGetCompositeQueryParam(); useEffect(() => { - if (!compositeQuery) { - resetQuery(defaultQuery); - redirectWithQueryBuilderData(defaultQuery); + if (!compositeQuery || forceReset) { + resetQuery(defaultValue); + redirectWithQueryBuilderData(defaultValue); } }, [ - defaultQuery, + defaultValue, urlQuery, redirectWithQueryBuilderData, compositeQuery, resetQuery, + forceReset, ]); }; diff --git a/frontend/src/pages/LiveLogs/index.tsx b/frontend/src/pages/LiveLogs/index.tsx index 0a11b33764bb..d354cfceca5f 100644 --- a/frontend/src/pages/LiveLogs/index.tsx +++ b/frontend/src/pages/LiveLogs/index.tsx @@ -9,7 +9,7 @@ import { useEffect } from 'react'; import { DataSource } from 'types/common/queryBuilder'; function LiveLogs(): JSX.Element { - useShareBuilderUrl(liveLogsCompositeQuery); + useShareBuilderUrl({ defaultValue: liveLogsCompositeQuery }); const { handleSetConfig } = useQueryBuilder(); useEffect(() => { diff --git a/frontend/src/pages/LogsExplorer/index.tsx b/frontend/src/pages/LogsExplorer/index.tsx index 44fbce891248..01a68e03264f 100644 --- a/frontend/src/pages/LogsExplorer/index.tsx +++ b/frontend/src/pages/LogsExplorer/index.tsx @@ -9,7 +9,7 @@ import QuickFilters from 'components/QuickFilters/QuickFilters'; import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types'; import { LOCALSTORAGE } from 'constants/localStorage'; import { QueryParams } from 'constants/query'; -import { PANEL_TYPES } from 'constants/queryBuilder'; +import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import LogExplorerQuerySection from 'container/LogExplorerQuerySection'; import LogsExplorerViewsContainer from 'container/LogsExplorerViews'; import { @@ -23,6 +23,7 @@ import RightToolbarActions from 'container/QueryBuilder/components/ToolbarAction import Toolbar from 'container/Toolbar/Toolbar'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange'; import useUrlQueryData from 'hooks/useUrlQueryData'; import { isEqual, isNull } from 'lodash-es'; @@ -77,7 +78,11 @@ function LogsExplorer(): JSX.Element { window.history.replaceState({}, '', url.toString()); }, [selectedView]); - const { handleRunQuery, handleSetConfig } = useQueryBuilder(); + const { + handleRunQuery, + handleSetConfig, + updateAllQueriesOperators, + } = useQueryBuilder(); const { handleExplorerTabChange } = useHandleExplorerTabChange(); @@ -87,12 +92,18 @@ function LogsExplorer(): JSX.Element { const [isLoadingQueries, setIsLoadingQueries] = useState(false); + const [shouldReset, setShouldReset] = useState(false); + const handleChangeSelectedView = useCallback( (view: ExplorerViews): void => { if (selectedView === ExplorerViews.LIST) { handleSetConfig(PANEL_TYPES.LIST, DataSource.LOGS); } + if (view === ExplorerViews.LIST) { + setShouldReset(true); + } + setSelectedView(view); handleExplorerTabChange( view === ExplorerViews.TIMESERIES ? PANEL_TYPES.TIME_SERIES : view, @@ -101,6 +112,25 @@ function LogsExplorer(): JSX.Element { [handleSetConfig, handleExplorerTabChange, selectedView], ); + const defaultListQuery = useMemo( + () => + updateAllQueriesOperators( + initialQueriesMap.logs, + PANEL_TYPES.LIST, + DataSource.LOGS, + ), + [updateAllQueriesOperators], + ); + + useShareBuilderUrl({ + defaultValue: defaultListQuery, + forceReset: shouldReset, + }); + + useEffect(() => { + if (shouldReset) setShouldReset(false); + }, [shouldReset]); + const handleFilterVisibilityChange = (): void => { setLocalStorageApi( LOCALSTORAGE.SHOW_LOGS_QUICK_FILTERS, @@ -286,7 +316,7 @@ function LogsExplorer(): JSX.Element { } rightActions={ handleRunQuery(true, true)} listQueryKeyRef={listQueryKeyRef} chartQueryKeyRef={chartQueryKeyRef} isLoadingQueries={isLoadingQueries} diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index 886106358fb9..e6675240e3d0 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -92,10 +92,16 @@ function TracesExplorer(): JSX.Element { }); }, [selectedView, setSearchParams]); + const [shouldReset, setShouldReset] = useState(false); + const handleChangeSelectedView = useCallback( (view: ExplorerViews): void => { if (selectedView === ExplorerViews.LIST) { - handleSetConfig(PANEL_TYPES.LIST, DataSource.LOGS); + handleSetConfig(PANEL_TYPES.LIST, DataSource.TRACES); + } + + if (view === ExplorerViews.LIST) { + setShouldReset(true); } setSelectedView(view); @@ -177,7 +183,11 @@ function TracesExplorer(): JSX.Element { [exportDefaultQuery, panelType, safeNavigate, getUpdatedQueryForExport], ); - useShareBuilderUrl(defaultQuery); + useShareBuilderUrl({ defaultValue: defaultQuery, forceReset: shouldReset }); + + useEffect(() => { + if (shouldReset) setShouldReset(false); + }, [shouldReset]); const [isOpen, setOpen] = useState(true); const logEventCalledRef = useRef(false); @@ -263,7 +273,11 @@ function TracesExplorer(): JSX.Element { onChangeSelectedView={handleChangeSelectedView} /> } - rightActions={} + rightActions={ + handleRunQuery(true, true)} + /> + } /> diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index f95f04a8c254..26c765974255 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -853,18 +853,35 @@ export function QueryBuilderProvider({ ); const handleRunQuery = useCallback( - (shallUpdateStepInterval?: boolean) => { + (shallUpdateStepInterval?: boolean, newQBQuery?: boolean) => { + let currentQueryData = currentQuery; + if (newQBQuery) { + currentQueryData = { + ...currentQueryData, + builder: { + ...currentQueryData.builder, + queryData: currentQueryData.builder.queryData.map((item) => ({ + ...item, + filters: { + items: [], + op: 'AND', + }, + having: [], + })), + }, + }; + } redirectWithQueryBuilderData({ ...{ - ...currentQuery, + ...currentQueryData, ...updateStepInterval( { - builder: currentQuery.builder, - clickhouse_sql: currentQuery.clickhouse_sql, - promql: currentQuery.promql, - id: currentQuery.id, + builder: currentQueryData.builder, + clickhouse_sql: currentQueryData.clickhouse_sql, + promql: currentQueryData.promql, + id: currentQueryData.id, queryType, - unit: currentQuery.unit, + unit: currentQueryData.unit, }, maxTime, minTime, diff --git a/frontend/src/types/api/v5/queryRange.ts b/frontend/src/types/api/v5/queryRange.ts index bc7a56bbaa26..21e5f3996d7f 100644 --- a/frontend/src/types/api/v5/queryRange.ts +++ b/frontend/src/types/api/v5/queryRange.ts @@ -111,6 +111,15 @@ export type SpaceAggregation = export type ColumnType = 'group' | 'aggregation'; +// ===================== Variable Types ===================== + +export type VariableType = 'query' | 'dynamic' | 'custom' | 'text'; + +export interface VariableItem { + type?: VariableType; + value: any; // eslint-disable-line @typescript-eslint/no-explicit-any +} + // ===================== Core Interface Types ===================== export interface TelemetryFieldKey { @@ -174,6 +183,7 @@ export interface MetricAggregation { temporality: Temporality; timeAggregation: TimeAggregation; spaceAggregation: SpaceAggregation; + reduceTo?: string; } export interface SecondaryAggregation { @@ -230,6 +240,9 @@ export interface QueryBuilderFormula { name: string; expression: string; functions?: QueryFunction[]; + order?: OrderBy[]; + limit?: number; + having?: Having; } export interface QueryBuilderJoin { @@ -254,8 +267,8 @@ export interface PromQuery { name: string; query: string; disabled?: boolean; - stats?: boolean; step?: Step; + stats?: boolean; } export interface ClickHouseQuery { @@ -288,7 +301,11 @@ export interface QueryRangeRequestV5 { end: number; // epoch milliseconds requestType: RequestType; compositeQuery: CompositeQuery; - variables?: Record; // eslint-disable-line @typescript-eslint/no-explicit-any + variables?: Record; + formatOptions?: { + formatTableResultForUI: boolean; + fillGaps?: boolean; + }; } // ===================== Response Types ===================== @@ -313,6 +330,7 @@ export interface TimeSeriesValue { value: number; values?: number[]; // For heatmap type charts bucket?: Bucket; + partial?: boolean; } export interface TimeSeries { @@ -336,6 +354,9 @@ export interface ColumnDescriptor extends TelemetryFieldKey { queryName: string; aggregationIndex: number; columnType: ColumnType; + meta?: { + unit?: string; + }; } export interface ScalarData { diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts index 0ff38cc4a0c3..09a6d35d0bd8 100644 --- a/frontend/src/types/common/queryBuilder.ts +++ b/frontend/src/types/common/queryBuilder.ts @@ -227,7 +227,10 @@ export type QueryBuilderContextType = { redirectToUrl?: typeof ROUTES[keyof typeof ROUTES], shallStringify?: boolean, ) => void; - handleRunQuery: (shallUpdateStepInterval?: boolean) => void; + handleRunQuery: ( + shallUpdateStepInterval?: boolean, + newQBQuery?: boolean, + ) => void; resetQuery: (newCurrentQuery?: QueryState) => void; handleOnUnitsChange: (units: Format['id']) => void; updateAllQueriesOperators: (