feat: new query builder misc fixes (#8359)

* feat: qb fixes

* feat: fixed handlerunquery props

* feat: fixes logs list order by

* feat: fix logs order by issue

* feat: safety check and order by correction

* feat: updated version in new create dashboards

* feat: added new formatOptions for table and fixed the pie chart plotting

* feat: keyboard shortcut overriding issue and pie ch correction in dashboard views

* feat: fixed dashboard data state management across datasource * paneltypes

* feat: fixed explorer pages data management issues

* feat: integrated new backend payload/request diff, to the UI types

* feat: fixed the collapse behaviour of QB - queries

* feat: fix order by and default aggregation to count()
This commit is contained in:
SagarRajput-7 2025-06-25 16:18:15 +05:30 committed by ahrefabhi
parent 26802cd668
commit a71a2af0a8
34 changed files with 570 additions and 335 deletions

View File

@ -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<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 {
): 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<string, any> = {};
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.

View File

@ -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<string, string> = {};
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<string, VariableItem>),
};
return { legendMap, queryPayload };

View File

@ -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<EditorView | null>(null);
@ -316,6 +345,7 @@ function HavingFilter({
extensions={[
havingAutocomplete,
javascript({ jsx: false, typescript: false }),
stopEventsExtension,
keymap.of([
...completionKeymap,
{

View File

@ -48,10 +48,12 @@ function QueryAggregationOptions({
<div className="query-aggregation-interval-label">every</div>
<div className="query-aggregation-interval-input-container">
<InputWithLabel
initialValue={queryData.stepInterval ? queryData.stepInterval : '60'}
initialValue={
queryData.stepInterval ? queryData.stepInterval : undefined
}
className="query-aggregation-interval-input"
label="Seconds"
placeholder="60"
placeholder="Auto"
type="number"
onChange={handleAggregationIntervalChange}
labelAfter

View File

@ -112,6 +112,30 @@ function getFunctionContextAtCursor(
return null;
}
// 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;
},
});
// 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,
{

View File

@ -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);

View File

@ -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({
</div>
)}
<div className="qb-elements-container">
<div className="qb-search-container">
{dataSource === DataSource.METRICS && (
<div className="metrics-select-container">
<MetricsSelect query={query} index={0} version="v4" />
</div>
)}
<div className="qb-search-filter-container">
<div className="query-search-container">
<QuerySearch
key={`query-search-${query.queryName}-${query.dataSource}`}
onChange={handleSearchChange}
queryData={query}
dataSource={dataSource}
/>
</div>
{showSpanScopeSelector && (
<div className="traces-search-filter-container">
<div className="traces-search-filter-in">in</div>
<SpanScopeSelector query={query} />
{!isCollapsed && (
<div className="qb-elements-container">
<div className="qb-search-container">
{dataSource === DataSource.METRICS && (
<div className="metrics-select-container">
<MetricsSelect query={query} index={index} version="v4" />
</div>
)}
</div>
</div>
{!showOnlyWhereClause &&
!isListViewPanel &&
dataSource !== DataSource.METRICS && (
<QueryAggregation
dataSource={dataSource}
key={`query-search-${query.queryName}-${query.dataSource}`}
panelType={panelType || undefined}
onAggregationIntervalChange={handleChangeAggregateEvery}
onChange={handleChangeAggregation}
queryData={query}
<div className="qb-search-filter-container">
<div className="query-search-container">
<QuerySearch
key={`query-search-${query.queryName}-${query.dataSource}`}
onChange={handleSearchChange}
queryData={query}
dataSource={dataSource}
/>
</div>
{showSpanScopeSelector && (
<div className="traces-search-filter-container">
<div className="traces-search-filter-in">in</div>
<SpanScopeSelector query={query} />
</div>
)}
</div>
</div>
{!showOnlyWhereClause &&
!isListViewPanel &&
dataSource !== DataSource.METRICS && (
<QueryAggregation
dataSource={dataSource}
key={`query-search-${query.queryName}-${query.dataSource}`}
panelType={panelType || undefined}
onAggregationIntervalChange={handleChangeAggregateEvery}
onChange={handleChangeAggregation}
queryData={query}
/>
)}
{!showOnlyWhereClause && dataSource === DataSource.METRICS && (
<MetricsAggregateSection
panelType={panelType}
query={query}
index={index}
key={`metrics-aggregate-section-${query.queryName}-${query.dataSource}`}
version="v4"
/>
)}
{!showOnlyWhereClause && dataSource === DataSource.METRICS && (
<MetricsAggregateSection
panelType={panelType}
query={query}
index={0}
key={`metrics-aggregate-section-${query.queryName}-${query.dataSource}`}
version="v4"
/>
)}
{!showOnlyWhereClause && (
<QueryAddOns
index={index}
query={query}
version="v3"
isListViewPanel={isListViewPanel}
showReduceTo={showReduceTo}
panelType={panelType}
/>
)}
</div>
{!showOnlyWhereClause && (
<QueryAddOns
index={index}
query={query}
version="v3"
isListViewPanel={isListViewPanel}
showReduceTo={showReduceTo}
panelType={panelType}
/>
)}
</div>
)}
</div>
</div>
);

View File

@ -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({

View File

@ -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,
},
},
},
],
],
},
},
});

View File

@ -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();

View File

@ -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}

View File

@ -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);

View File

@ -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 ||

View File

@ -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}
/>

View File

@ -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(

View File

@ -34,7 +34,7 @@ function LogExplorerQuerySection({
[updateAllQueriesOperators],
);
useShareBuilderUrl(defaultValue);
useShareBuilderUrl({ defaultValue });
const filterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
const isTable = panelTypes === PANEL_TYPES.TABLE;

View File

@ -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(() => {

View File

@ -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 {
</div>
<div className="explore-header-right-actions">
<DateTimeSelector showAutoRefresh />
<RightToolbarActions onStageRunQuery={handleRunQuery} />
<RightToolbarActions
onStageRunQuery={(): void => handleRunQuery(true, true)}
/>
</div>
</div>
{/* <QuerySection /> */}

View File

@ -64,7 +64,7 @@ function QuerySection({
const { query } = selectedWidget;
useShareBuilderUrl(query);
useShareBuilderUrl({ defaultValue: query });
const handleStageQuery = useCallback(
(query: Query): void => {

View File

@ -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 <Card>Invalid widget</Card>;
}

View File

@ -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<MetricRangePayloadProps, unknown>,
): SuccessResponse<MetricRangePayloadProps, unknown> {
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<string, string> = {};
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;
}

View File

@ -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,

View File

@ -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;
}),
},

View File

@ -450,7 +450,7 @@ function QueryBuilderSearchV2(
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
handleRunQuery();
handleRunQuery(false, true);
setIsOpen(false);
}
},

View File

@ -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: '',
},
],
],
},
},
});

View File

@ -31,7 +31,7 @@ const KeyboardHotkeysContext = createContext<KeyboardHotkeysContextReturnValue>(
},
);
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;
}

View File

@ -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;
}

View File

@ -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,
]);
};

View File

@ -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(() => {

View File

@ -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<boolean>(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={
<RightToolbarActions
onStageRunQuery={handleRunQuery}
onStageRunQuery={(): void => handleRunQuery(true, true)}
listQueryKeyRef={listQueryKeyRef}
chartQueryKeyRef={chartQueryKeyRef}
isLoadingQueries={isLoadingQueries}

View File

@ -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<boolean>(true);
const logEventCalledRef = useRef(false);
@ -263,7 +273,11 @@ function TracesExplorer(): JSX.Element {
onChangeSelectedView={handleChangeSelectedView}
/>
}
rightActions={<RightToolbarActions onStageRunQuery={handleRunQuery} />}
rightActions={
<RightToolbarActions
onStageRunQuery={(): void => handleRunQuery(true, true)}
/>
}
/>
</div>
<ExplorerCard sourcepage={DataSource.TRACES}>

View File

@ -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,

View File

@ -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<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
variables?: Record<string, VariableItem>;
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 {

View File

@ -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: (