From 555bb79866f836aaad34636494ef6af90fbfc37e Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Thu, 22 Jun 2023 15:00:31 +0530 Subject: [PATCH 01/29] =?UTF-8?q?chore:=20=F0=9F=94=A7=20use=20signoz/locu?= =?UTF-8?q?st=20docker=20repo=20for=20multi-arch=20image=20(#2954)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/docker-swarm/clickhouse-setup/docker-compose.yaml | 2 +- deploy/docker/clickhouse-setup/docker-compose-core.yaml | 2 +- deploy/docker/clickhouse-setup/docker-compose.yaml | 2 +- pkg/query-service/tests/test-deploy/docker-compose.yaml | 2 +- sample-apps/hotrod/hotrod-install.sh | 2 +- sample-apps/hotrod/hotrod.yaml | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index fec809c500..bd34be3364 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -233,7 +233,7 @@ services: max-file: "3" load-hotrod: - image: "grubykarol/locust:1.2.3-python3.9-alpine3.12" + image: "signoz/locust:1.2.3" hostname: load-hotrod environment: ATTACKED_HOST: http://hotrod:8080 diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml index 84d2bdcd93..5e2db19d89 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml @@ -93,7 +93,7 @@ services: - JAEGER_ENDPOINT=http://otel-collector:14268/api/traces load-hotrod: - image: "grubykarol/locust:1.2.3-python3.9-alpine3.12" + image: "signoz/locust:1.2.3" container_name: load-hotrod hostname: load-hotrod environment: diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 70441af123..14709f7c34 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -243,7 +243,7 @@ services: - JAEGER_ENDPOINT=http://otel-collector:14268/api/traces load-hotrod: - image: "grubykarol/locust:1.2.3-python3.9-alpine3.12" + image: "signoz/locust:1.2.3" container_name: load-hotrod hostname: load-hotrod environment: diff --git a/pkg/query-service/tests/test-deploy/docker-compose.yaml b/pkg/query-service/tests/test-deploy/docker-compose.yaml index 057fa66ba2..faaae9a717 100644 --- a/pkg/query-service/tests/test-deploy/docker-compose.yaml +++ b/pkg/query-service/tests/test-deploy/docker-compose.yaml @@ -219,7 +219,7 @@ services: - JAEGER_ENDPOINT=http://otel-collector:14268/api/traces load-hotrod: - image: "grubykarol/locust:1.2.3-python3.9-alpine3.12" + image: "signoz/locust:1.2.3" container_name: load-hotrod hostname: load-hotrod environment: diff --git a/sample-apps/hotrod/hotrod-install.sh b/sample-apps/hotrod/hotrod-install.sh index dc839d0535..197f18d76e 100755 --- a/sample-apps/hotrod/hotrod-install.sh +++ b/sample-apps/hotrod/hotrod-install.sh @@ -15,7 +15,7 @@ fi # Locust's docker image if [[ -z $LOCUST_IMAGE ]]; then - LOCUST_REPO="${LOCUST_REPO:-grubykarol/locust}" + LOCUST_REPO="${LOCUST_REPO:-signoz/locust}" LOCUST_TAG="${LOCUST_TAG:-0.8.1-py3.6}" LOCUST_IMAGE="${LOCUST_REPO}:${LOCUST_TAG}" fi diff --git a/sample-apps/hotrod/hotrod.yaml b/sample-apps/hotrod/hotrod.yaml index 63d7fc88de..0793ec3d95 100644 --- a/sample-apps/hotrod/hotrod.yaml +++ b/sample-apps/hotrod/hotrod.yaml @@ -96,7 +96,7 @@ spec: role: locust-master spec: containers: - - image: grubykarol/locust:0.8.1-py3.6 + - image: signoz/locust:1.2.3 imagePullPolicy: IfNotPresent name: locust-master env: @@ -173,7 +173,7 @@ spec: role: locust-slave spec: containers: - - image: grubykarol/locust:0.8.1-py3.6 + - image: signoz/locust:1.2.3 imagePullPolicy: IfNotPresent name: locust-slave env: From c05c939ee113ac38244643a05ee23deb27edd49e Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 22 Jun 2023 17:58:56 +0530 Subject: [PATCH 02/29] feat: sorting for tooltip in graph view (#2948) * feat: sorting for tooltip in graph view * Update index.tsx * refactor: name of the variable in itemSort --------- Co-authored-by: Palash Gupta --- frontend/src/components/Graph/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index f4ea8b14b2..6f0f475bac 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -181,6 +181,9 @@ function Graph({ }, }, position: 'custom', + itemSort(item1, item2) { + return item2.parsed.y - item1.parsed.y; + }, }, [dragSelectPluginId]: createDragSelectPluginOptions( !!onDragSelect, From 314edaf1dfb9d5425da19eabf443f35561ba62a1 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 23 Jun 2023 11:22:34 +0530 Subject: [PATCH 03/29] fix: default as query builder when creating new alert (#2963) --- frontend/src/container/CreateAlertRule/defaults.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/container/CreateAlertRule/defaults.ts b/frontend/src/container/CreateAlertRule/defaults.ts index ce73dda62b..adc7c9154d 100644 --- a/frontend/src/container/CreateAlertRule/defaults.ts +++ b/frontend/src/container/CreateAlertRule/defaults.ts @@ -38,7 +38,7 @@ export const alertDefaults: AlertDef = { disabled: false, }, }, - queryType: EQueryType.CLICKHOUSE, + queryType: EQueryType.QUERY_BUILDER, panelType: PANEL_TYPES.TIME_SERIES, }, op: defaultCompareOp, @@ -67,7 +67,7 @@ export const logAlertDefaults: AlertDef = { disabled: false, }, }, - queryType: EQueryType.CLICKHOUSE, + queryType: EQueryType.QUERY_BUILDER, panelType: PANEL_TYPES.TIME_SERIES, }, op: defaultCompareOp, @@ -97,7 +97,7 @@ export const traceAlertDefaults: AlertDef = { disabled: false, }, }, - queryType: EQueryType.CLICKHOUSE, + queryType: EQueryType.QUERY_BUILDER, panelType: PANEL_TYPES.TIME_SERIES, }, op: defaultCompareOp, @@ -127,7 +127,7 @@ export const exceptionAlertDefaults: AlertDef = { disabled: false, }, }, - queryType: EQueryType.CLICKHOUSE, + queryType: EQueryType.QUERY_BUILDER, panelType: PANEL_TYPES.TIME_SERIES, }, op: defaultCompareOp, From 522bdf04ef98e593b866444d2f99398f65fdbf41 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:15:09 +0300 Subject: [PATCH 04/29] feat: Add Generic Table View in the logs explorer (#2936) * feat: add dynamic table based on query * fix: group by repeating * fix: change view when groupBy exist in the list * fix: table scroll * fix: filters for explorer page (#2959) --------- Co-authored-by: Palash Gupta --- .../components/ResizeTable/ResizeTable.tsx | 16 +- .../LogsExplorerTable/LogsExplorerTable.tsx | 44 +++ .../src/container/LogsExplorerTable/index.ts | 1 + .../LogsExplorerViews/LogsExplorerViews.tsx | 22 +- .../QueryBuilder/components/Query/Query.tsx | 49 ++- .../filters/GroupByFilter/GroupByFilter.tsx | 10 +- .../QueryTable/QueryTable.intefaces.ts | 14 + .../src/container/QueryTable/QueryTable.tsx | 52 ++++ frontend/src/container/QueryTable/index.ts | 1 + .../newQueryBuilder/convertNewDataToOld.ts | 6 +- .../lib/query/createTableColumnsFromQuery.ts | 294 ++++++++++++++++++ frontend/src/lib/toCapitalize.ts | 2 + .../src/types/api/metrics/getQueryRange.ts | 1 + frontend/src/types/api/widgets/getQuery.ts | 2 +- 14 files changed, 500 insertions(+), 14 deletions(-) create mode 100644 frontend/src/container/LogsExplorerTable/LogsExplorerTable.tsx create mode 100644 frontend/src/container/LogsExplorerTable/index.ts create mode 100644 frontend/src/container/QueryTable/QueryTable.intefaces.ts create mode 100644 frontend/src/container/QueryTable/QueryTable.tsx create mode 100644 frontend/src/container/QueryTable/index.ts create mode 100644 frontend/src/lib/query/createTableColumnsFromQuery.ts create mode 100644 frontend/src/lib/toCapitalize.ts diff --git a/frontend/src/components/ResizeTable/ResizeTable.tsx b/frontend/src/components/ResizeTable/ResizeTable.tsx index 681d8b8670..d6898d0815 100644 --- a/frontend/src/components/ResizeTable/ResizeTable.tsx +++ b/frontend/src/components/ResizeTable/ResizeTable.tsx @@ -1,14 +1,20 @@ import { Table } from 'antd'; import type { TableProps } from 'antd/es/table'; import { ColumnsType } from 'antd/lib/table'; -import { SyntheticEvent, useCallback, useMemo, useState } from 'react'; +import { + SyntheticEvent, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; import { ResizeCallbackData } from 'react-resizable'; import ResizableHeader from './ResizableHeader'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function ResizeTable({ columns, ...restprops }: TableProps): JSX.Element { - const [columnsData, setColumns] = useState(columns || []); + const [columnsData, setColumns] = useState([]); const handleResize = useCallback( (index: number) => ( @@ -37,6 +43,12 @@ function ResizeTable({ columns, ...restprops }: TableProps): JSX.Element { [columnsData, handleResize], ); + useEffect(() => { + if (columns) { + setColumns(columns); + } + }, [columns]); + return ( ( + (state) => state.globalTime, + ); + + const panelTypeParam = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); + + const { data, isFetching } = useGetQueryRange( + { + query: stagedQuery || initialQueriesMap.metrics, + graphType: panelTypeParam, + globalSelectedInterval: selectedTime, + selectedTime: 'GLOBAL_TIME', + }, + { + queryKey: [ + REACT_QUERY_KEY.GET_QUERY_RANGE, + selectedTime, + stagedQuery, + panelTypeParam, + ], + enabled: !!stagedQuery, + }, + ); + return ( + + ); +} diff --git a/frontend/src/container/LogsExplorerTable/index.ts b/frontend/src/container/LogsExplorerTable/index.ts new file mode 100644 index 0000000000..4a9b82e594 --- /dev/null +++ b/frontend/src/container/LogsExplorerTable/index.ts @@ -0,0 +1 @@ +export { LogsExplorerTable } from './LogsExplorerTable'; diff --git a/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx b/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx index 303db79f01..76765959b0 100644 --- a/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx +++ b/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx @@ -1,6 +1,7 @@ import { TabsProps } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames'; +import { LogsExplorerTable } from 'container/LogsExplorerTable'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; @@ -25,17 +26,26 @@ export function LogsExplorerViews(): JSX.Element { [currentQuery], ); + const isGroupByExist = useMemo(() => { + const groupByCount: number = currentQuery.builder.queryData.reduce( + (acc, query) => acc + query.groupBy.length, + 0, + ); + + return groupByCount > 0; + }, [currentQuery]); + const tabsItems: TabsProps['items'] = useMemo( () => [ { label: 'List View', key: PANEL_TYPES.LIST, - disabled: isMultipleQueries, + disabled: isMultipleQueries || isGroupByExist, }, { label: 'TimeSeries', key: PANEL_TYPES.TIME_SERIES }, - { label: 'Table', key: PANEL_TYPES.TABLE }, + { label: 'Table', key: PANEL_TYPES.TABLE, children: }, ], - [isMultipleQueries], + [isMultipleQueries, isGroupByExist], ); const handleChangeView = useCallback( @@ -57,10 +67,12 @@ export function LogsExplorerViews(): JSX.Element { ); useEffect(() => { - if (panelTypeParams === 'list' && isMultipleQueries) { + const shouldChangeView = isMultipleQueries || isGroupByExist; + + if (panelTypeParams === 'list' && shouldChangeView) { handleChangeView(PANEL_TYPES.TIME_SERIES); } - }, [panelTypeParams, isMultipleQueries, handleChangeView]); + }, [panelTypeParams, isMultipleQueries, isGroupByExist, handleChangeView]); return (
diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index 5db70136cc..ae2fb929b1 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -196,7 +196,54 @@ export const Query = memo(function Query({ } default: { - return null; + return ( + <> +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } } }, [ diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx index 111ff2ea5c..62bd165ffb 100644 --- a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx @@ -102,10 +102,12 @@ export const GroupByFilter = memo(function GroupByFilter({ return { id, - key, - dataType: dataType as DataType, - type: type as AutocompleteType, - isColumn: isColumn === 'true', + key: key || currentValue, + dataType: (dataType as DataType) || initialAutocompleteData.dataType, + type: (type as AutocompleteType) || initialAutocompleteData.type, + isColumn: isColumn + ? isColumn === 'true' + : initialAutocompleteData.isColumn, }; } diff --git a/frontend/src/container/QueryTable/QueryTable.intefaces.ts b/frontend/src/container/QueryTable/QueryTable.intefaces.ts new file mode 100644 index 0000000000..2abc353997 --- /dev/null +++ b/frontend/src/container/QueryTable/QueryTable.intefaces.ts @@ -0,0 +1,14 @@ +import { TableProps } from 'antd'; +import { RowData } from 'lib/query/createTableColumnsFromQuery'; +import { ReactNode } from 'react'; +import { Query } from 'types/api/queryBuilder/queryBuilderData'; +import { QueryDataV3 } from 'types/api/widgets/getQuery'; + +export type QueryTableProps = Omit< + TableProps, + 'columns' | 'dataSource' +> & { + queryTableData: QueryDataV3[]; + query: Query; + renderActionCell?: (record: RowData) => ReactNode; +}; diff --git a/frontend/src/container/QueryTable/QueryTable.tsx b/frontend/src/container/QueryTable/QueryTable.tsx new file mode 100644 index 0000000000..8265a8494d --- /dev/null +++ b/frontend/src/container/QueryTable/QueryTable.tsx @@ -0,0 +1,52 @@ +import type { ColumnsType } from 'antd/es/table'; +import { ResizeTable } from 'components/ResizeTable'; +import dayjs from 'dayjs'; +import { + createTableColumnsFromQuery, + RowData, +} from 'lib/query/createTableColumnsFromQuery'; +import { useMemo } from 'react'; + +import { QueryTableProps } from './QueryTable.intefaces'; + +export function QueryTable({ + queryTableData, + query, + renderActionCell, + ...props +}: QueryTableProps): JSX.Element { + const { columns, dataSource } = useMemo( + () => + createTableColumnsFromQuery({ + query, + queryTableData, + renderActionCell, + }), + [query, queryTableData, renderActionCell], + ); + + const modifiedColumns = useMemo(() => { + const currentColumns: ColumnsType = columns.map((column) => + column.key === 'timestamp' + ? { + ...column, + render: (_, record): string => + dayjs(new Date(record.timestamp)).format('MMM DD, YYYY, HH:mm:ss'), + } + : column, + ); + + return currentColumns; + }, [columns]); + + return ( + + ); +} diff --git a/frontend/src/container/QueryTable/index.ts b/frontend/src/container/QueryTable/index.ts new file mode 100644 index 0000000000..bb785ff437 --- /dev/null +++ b/frontend/src/container/QueryTable/index.ts @@ -0,0 +1 @@ +export { QueryTable } from './QueryTable'; diff --git a/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts b/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts index dcb103d6cb..eed4f83da3 100644 --- a/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts +++ b/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts @@ -35,5 +35,9 @@ export const convertNewDataToOld = ( }); const oldResultType = resultType; - return { data: { result: oldResult, resultType: oldResultType } }; + // TODO: fix it later for using only v3 version of api + + return { + data: { result: oldResult, resultType: oldResultType, newResult: newData }, + }; }; diff --git a/frontend/src/lib/query/createTableColumnsFromQuery.ts b/frontend/src/lib/query/createTableColumnsFromQuery.ts new file mode 100644 index 0000000000..bbb03e284d --- /dev/null +++ b/frontend/src/lib/query/createTableColumnsFromQuery.ts @@ -0,0 +1,294 @@ +import { ColumnsType } from 'antd/es/table'; +import { ColumnType } from 'antd/lib/table'; +import { FORMULA_REGEXP } from 'constants/regExp'; +import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces'; +import { toCapitalize } from 'lib/toCapitalize'; +import { ReactNode } from 'react'; +import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData'; +import { QueryDataV3, SeriesItem } from 'types/api/widgets/getQuery'; +import { v4 as uuid } from 'uuid'; + +type CreateTableDataFromQueryParams = Pick< + QueryTableProps, + 'queryTableData' | 'query' | 'renderActionCell' +>; + +export type RowData = { + timestamp: number; + key: string; + [key: string]: string | number; +}; + +type DynamicColumn = { + key: keyof RowData; + data: (string | number)[]; + type: 'field' | 'operator'; + sortable: boolean; +}; + +type DynamicColumns = DynamicColumn[]; + +type CreateTableDataFromQuery = ( + params: CreateTableDataFromQueryParams, +) => { + columns: ColumnsType; + dataSource: RowData[]; + rowsLength: number; +}; + +type FillColumnData = ( + queryTableData: QueryDataV3[], + dynamicColumns: DynamicColumns, + query: Query, +) => { filledDynamicColumns: DynamicColumns; rowsLength: number }; + +type GetDynamicColumns = ( + queryTableData: QueryDataV3[], + query: Query, +) => DynamicColumns; + +const isFormula = (queryName: string): boolean => + FORMULA_REGEXP.test(queryName); + +const isColumnExist = ( + columnName: string, + columns: DynamicColumns, +): boolean => { + const columnKeys = columns.map((item) => item.key); + + return columnKeys.includes(columnName); +}; + +const prepareColumnTitle = (title: string): string => { + const haveUnderscore = title.includes('_'); + + if (haveUnderscore) { + return title + .split('_') + .map((str) => toCapitalize(str)) + .join(' '); + } + + return toCapitalize(title); +}; + +const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => { + const dynamicColumns: DynamicColumns = []; + + queryTableData.forEach((currentQuery) => { + if (!currentQuery.series) return; + + if (!isColumnExist('timestamp', dynamicColumns)) { + dynamicColumns.push({ + key: 'timestamp', + data: [], + type: 'field', + sortable: true, + }); + } + + currentQuery.series.forEach((seria) => { + Object.keys(seria.labels).forEach((label) => { + if (isColumnExist(label, dynamicColumns)) return; + if (isFormula(label)) return; + + const labelValue = seria.labels[label]; + + const isNumber = !Number.isNaN(parseFloat(labelValue)); + + const fieldObj: DynamicColumn = { + key: label, + data: [], + type: 'field', + sortable: isNumber, + }; + + dynamicColumns.push(fieldObj); + }); + }); + + if (!isFormula(currentQuery.queryName)) { + const builderQuery = query.builder.queryData.find( + (q) => q.queryName === currentQuery.queryName, + ); + + const operator = builderQuery ? builderQuery.aggregateOperator : ''; + + if (isColumnExist(operator, dynamicColumns)) return; + + const operatorColumn: DynamicColumn = { + key: operator, + data: [], + type: 'operator', + sortable: true, + }; + dynamicColumns.push(operatorColumn); + } + }); + + return dynamicColumns; +}; + +const getQueryOperator = ( + queryData: IBuilderQuery[], + currentQueryName: string, +): string => { + const builderQuery = queryData.find((q) => q.queryName === currentQueryName); + + return builderQuery ? builderQuery.aggregateOperator : ''; +}; + +const fillEmptyRowCells = ( + unusedColumnsKeys: Set, + sourceColumns: DynamicColumns, + currentColumn: DynamicColumn, +): void => { + unusedColumnsKeys.forEach((key) => { + if (key === currentColumn.key) { + const unusedCol = sourceColumns.find((item) => item.key === key); + + if (unusedCol) { + unusedCol.data.push('N/A'); + unusedColumnsKeys.delete(key); + } + } + }); +}; + +const fillDataFromSeria = ( + seria: SeriesItem, + columns: DynamicColumns, + currentOperator: string, +): void => { + const labelEntries = Object.entries(seria.labels); + + seria.values.forEach((value) => { + const unusedColumnsKeys = new Set( + columns.map((item) => item.key), + ); + + columns.forEach((column) => { + if (isFormula(column.key as string)) return; + + if (column.key === 'timestamp') { + column.data.push(value.timestamp); + unusedColumnsKeys.delete('timestamp'); + return; + } + + if (column.key === currentOperator) { + column.data.push(parseFloat(value.value).toFixed(2)); + unusedColumnsKeys.delete(column.key); + return; + } + + labelEntries.forEach(([key, currentValue]) => { + if (column.key === key) { + column.data.push(currentValue); + unusedColumnsKeys.delete(key); + } + }); + + fillEmptyRowCells(unusedColumnsKeys, columns, column); + }); + }); +}; + +const fillColumnsData: FillColumnData = (queryTableData, cols, query) => { + const fields = cols.filter((item) => item.type === 'field'); + const operators = cols.filter((item) => item.type === 'operator'); + const resultColumns = [...fields, ...operators]; + + queryTableData.forEach((currentQuery) => { + if (!currentQuery.series) return; + + const currentOperator = getQueryOperator( + query.builder.queryData, + currentQuery.queryName, + ); + + currentQuery.series.forEach((seria) => { + fillDataFromSeria(seria, resultColumns, currentOperator); + }); + }); + + const rowsLength = resultColumns.length > 0 ? resultColumns[0].data.length : 0; + + return { filledDynamicColumns: resultColumns, rowsLength }; +}; + +const generateData = ( + dynamicColumns: DynamicColumns, + rowsLength: number, +): RowData[] => { + const data: RowData[] = []; + + for (let i = 0; i < rowsLength; i += 1) { + const rowData: RowData = dynamicColumns.reduce((acc, item) => { + const { key } = item; + + acc[key] = item.data[i]; + acc.key = uuid(); + + return acc; + }, {} as RowData); + + data.push(rowData); + } + + return data; +}; + +const generateTableColumns = ( + dynamicColumns: DynamicColumns, +): ColumnsType => { + const columns: ColumnsType = dynamicColumns.reduce< + ColumnsType + >((acc, item) => { + const column: ColumnType = { + dataIndex: item.key, + key: item.key, + title: prepareColumnTitle(item.key as string), + sorter: item.sortable + ? (a: RowData, b: RowData): number => + (a[item.key] as number) - (b[item.key] as number) + : false, + }; + + return [...acc, column]; + }, []); + + return columns; +}; + +export const createTableColumnsFromQuery: CreateTableDataFromQuery = ({ + query, + queryTableData, + renderActionCell, +}) => { + const dynamicColumns = getDynamicColumns(queryTableData, query); + + const { filledDynamicColumns, rowsLength } = fillColumnsData( + queryTableData, + dynamicColumns, + query, + ); + + const dataSource = generateData(filledDynamicColumns, rowsLength); + + const columns = generateTableColumns(filledDynamicColumns); + + const actionsCell: ColumnType | null = renderActionCell + ? { + key: 'actions', + title: 'Actions', + render: (_, record): ReactNode => renderActionCell(record), + } + : null; + + if (actionsCell && dataSource.length > 0) { + columns.push(actionsCell); + } + + return { columns, dataSource, rowsLength }; +}; diff --git a/frontend/src/lib/toCapitalize.ts b/frontend/src/lib/toCapitalize.ts new file mode 100644 index 0000000000..a42a7336bf --- /dev/null +++ b/frontend/src/lib/toCapitalize.ts @@ -0,0 +1,2 @@ +export const toCapitalize = (str: string): string => + str[0].toUpperCase() + str.slice(1); diff --git a/frontend/src/types/api/metrics/getQueryRange.ts b/frontend/src/types/api/metrics/getQueryRange.ts index f8c32c29a3..5dd80a451f 100644 --- a/frontend/src/types/api/metrics/getQueryRange.ts +++ b/frontend/src/types/api/metrics/getQueryRange.ts @@ -5,6 +5,7 @@ export interface MetricRangePayloadProps { data: { result: QueryData[]; resultType: string; + newResult: MetricRangePayloadV3; }; } diff --git a/frontend/src/types/api/widgets/getQuery.ts b/frontend/src/types/api/widgets/getQuery.ts index 60d679c36e..7452b52dda 100644 --- a/frontend/src/types/api/widgets/getQuery.ts +++ b/frontend/src/types/api/widgets/getQuery.ts @@ -23,7 +23,7 @@ export interface QueryDataV3 { list: null; queryName: string; legend?: string; - series: SeriesItem[]; + series: SeriesItem[] | null; } export interface Props { From bd18eee662d0097bc4dbf9717c7f78b37fb57eab Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:19:53 +0300 Subject: [PATCH 05/29] Fix/query builder updating (#2962) * feat: add dynamic table based on query * fix: group by repeating * fix: change view when groupBy exist in the list * feat: add list view for log explorer * fix: query builder updating * fix: table scroll * fix: filters for explorer page (#2959) --------- Co-authored-by: Prashant Shahi --- .../src/container/FormAlertRules/index.tsx | 2 +- .../src/container/LogsExplorerChart/index.ts | 1 - .../{LogsExplorerChart.tsx => index.tsx} | 22 +- .../LogsExplorerList.interfaces.ts | 3 + .../src/container/LogsExplorerList/index.tsx | 117 +++++++++ .../LogsExplorerTable.interfaces.ts | 6 + .../LogsExplorerTable/LogsExplorerTable.tsx | 44 ---- .../src/container/LogsExplorerTable/index.ts | 1 - .../src/container/LogsExplorerTable/index.tsx | 23 ++ .../LogsExplorerViews/LogsExplorerViews.tsx | 87 ------- .../src/container/LogsExplorerViews/index.ts | 1 - .../src/container/LogsExplorerViews/index.tsx | 124 ++++++++++ .../LeftContainer/QuerySection/index.tsx | 2 +- .../container/QueryBuilder/QueryBuilder.tsx | 30 ++- .../queryBuilder/useGetWidgetQueryRange.ts | 10 +- .../hooks/queryBuilder/useQueryOperations.ts | 22 +- .../hooks/queryBuilder/useShareBuilderUrl.ts | 10 +- frontend/src/pages/LogsExplorer/index.tsx | 19 +- frontend/src/pages/TracesExplorer/index.tsx | 2 +- frontend/src/providers/QueryBuilder.tsx | 223 +++++++++++------- frontend/src/types/api/widgets/getQuery.ts | 6 +- frontend/src/types/common/queryBuilder.ts | 14 +- 22 files changed, 491 insertions(+), 278 deletions(-) delete mode 100644 frontend/src/container/LogsExplorerChart/index.ts rename frontend/src/container/LogsExplorerChart/{LogsExplorerChart.tsx => index.tsx} (75%) create mode 100644 frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts create mode 100644 frontend/src/container/LogsExplorerList/index.tsx create mode 100644 frontend/src/container/LogsExplorerTable/LogsExplorerTable.interfaces.ts delete mode 100644 frontend/src/container/LogsExplorerTable/LogsExplorerTable.tsx delete mode 100644 frontend/src/container/LogsExplorerTable/index.ts create mode 100644 frontend/src/container/LogsExplorerTable/index.tsx delete mode 100644 frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx delete mode 100644 frontend/src/container/LogsExplorerViews/index.ts create mode 100644 frontend/src/container/LogsExplorerViews/index.tsx diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index 9db261afd0..26a0830226 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -70,7 +70,7 @@ function FormAlertRules({ const sq = useMemo(() => mapQueryDataFromApi(initQuery), [initQuery]); - useShareBuilderUrl({ defaultValue: sq }); + useShareBuilderUrl(sq); useEffect(() => { setAlertDef(initialValue); diff --git a/frontend/src/container/LogsExplorerChart/index.ts b/frontend/src/container/LogsExplorerChart/index.ts deleted file mode 100644 index 48d9469dba..0000000000 --- a/frontend/src/container/LogsExplorerChart/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LogsExplorerChart } from './LogsExplorerChart'; diff --git a/frontend/src/container/LogsExplorerChart/LogsExplorerChart.tsx b/frontend/src/container/LogsExplorerChart/index.tsx similarity index 75% rename from frontend/src/container/LogsExplorerChart/LogsExplorerChart.tsx rename to frontend/src/container/LogsExplorerChart/index.tsx index 6c2307efc2..02cc7606d7 100644 --- a/frontend/src/container/LogsExplorerChart/LogsExplorerChart.tsx +++ b/frontend/src/container/LogsExplorerChart/index.tsx @@ -2,41 +2,33 @@ import Graph from 'components/Graph'; import Spinner from 'components/Spinner'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { getExplorerChartData } from 'lib/explorer/getExplorerChartData'; -import { useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { GlobalReducer } from 'types/reducer/globalTime'; import { CardStyled } from './LogsExplorerChart.styled'; -export function LogsExplorerChart(): JSX.Element { - const { stagedQuery } = useQueryBuilder(); +function LogsExplorerChart(): JSX.Element { + const { stagedQuery, panelType, isEnabledQuery } = useQueryBuilder(); const { selectedTime } = useSelector( (state) => state.globalTime, ); - const panelTypeParam = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); - const { data, isFetching } = useGetQueryRange( { query: stagedQuery || initialQueriesMap.metrics, - graphType: panelTypeParam, + graphType: panelType || PANEL_TYPES.LIST, globalSelectedInterval: selectedTime, selectedTime: 'GLOBAL_TIME', }, { - queryKey: [ - REACT_QUERY_KEY.GET_QUERY_RANGE, - selectedTime, - stagedQuery, - panelTypeParam, - ], - enabled: !!stagedQuery, + queryKey: [REACT_QUERY_KEY.GET_QUERY_RANGE, selectedTime, stagedQuery], + enabled: isEnabledQuery, }, ); @@ -64,3 +56,5 @@ export function LogsExplorerChart(): JSX.Element { ); } + +export default memo(LogsExplorerChart); diff --git a/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts b/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts new file mode 100644 index 0000000000..4d0a534ad2 --- /dev/null +++ b/frontend/src/container/LogsExplorerList/LogsExplorerList.interfaces.ts @@ -0,0 +1,3 @@ +import { QueryDataV3 } from 'types/api/widgets/getQuery'; + +export type LogsExplorerListProps = { data: QueryDataV3[]; isLoading: boolean }; diff --git a/frontend/src/container/LogsExplorerList/index.tsx b/frontend/src/container/LogsExplorerList/index.tsx new file mode 100644 index 0000000000..cacc611f90 --- /dev/null +++ b/frontend/src/container/LogsExplorerList/index.tsx @@ -0,0 +1,117 @@ +import { Card, Typography } from 'antd'; +// components +import ListLogView from 'components/Logs/ListLogView'; +import RawLogView from 'components/Logs/RawLogView'; +import LogsTableView from 'components/Logs/TableView'; +import Spinner from 'components/Spinner'; +import { LogViewMode } from 'container/LogsTable'; +import { Container, Heading } from 'container/LogsTable/styles'; +import { contentStyle } from 'container/Trace/Search/config'; +import useFontFaceObserver from 'hooks/useFontObserver'; +import { memo, useCallback, useMemo, useState } from 'react'; +import { Virtuoso } from 'react-virtuoso'; +// interfaces +import { ILog } from 'types/api/logs/log'; + +import { LogsExplorerListProps } from './LogsExplorerList.interfaces'; + +function LogsExplorerList({ + data, + isLoading, +}: LogsExplorerListProps): JSX.Element { + const [viewMode] = useState('raw'); + const [linesPerRow] = useState(20); + + const logs: ILog[] = useMemo(() => { + if (data.length > 0 && data[0].list) { + const logs: ILog[] = data[0].list.map((item) => ({ + timestamp: +item.timestamp, + ...item.data, + })); + + return logs; + } + + return []; + }, [data]); + + useFontFaceObserver( + [ + { + family: 'Fira Code', + weight: '300', + }, + ], + viewMode === 'raw', + { + timeout: 5000, + }, + ); + // TODO: implement here linesPerRow, mode like in useSelectedLogView + + const getItemContent = useCallback( + (index: number): JSX.Element => { + const log = logs[index]; + + if (viewMode === 'raw') { + return ( + {}} + /> + ); + } + + return ; + }, + [logs, linesPerRow, viewMode], + ); + + const renderContent = useMemo(() => { + if (viewMode === 'table') { + return ( + {}} + /> + ); + } + + return ( + + + + ); + }, [getItemContent, linesPerRow, logs, viewMode]); + + if (isLoading) { + return ; + } + + return ( + + {viewMode !== 'table' && ( + + Event + + )} + + {logs.length === 0 && No logs lines found} + + {renderContent} + + ); +} + +export default memo(LogsExplorerList); diff --git a/frontend/src/container/LogsExplorerTable/LogsExplorerTable.interfaces.ts b/frontend/src/container/LogsExplorerTable/LogsExplorerTable.interfaces.ts new file mode 100644 index 0000000000..f2e4c3e7d6 --- /dev/null +++ b/frontend/src/container/LogsExplorerTable/LogsExplorerTable.interfaces.ts @@ -0,0 +1,6 @@ +import { QueryDataV3 } from 'types/api/widgets/getQuery'; + +export type LogsExplorerTableProps = { + data: QueryDataV3[]; + isLoading: boolean; +}; diff --git a/frontend/src/container/LogsExplorerTable/LogsExplorerTable.tsx b/frontend/src/container/LogsExplorerTable/LogsExplorerTable.tsx deleted file mode 100644 index 1ac4510c56..0000000000 --- a/frontend/src/container/LogsExplorerTable/LogsExplorerTable.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; -import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import { QueryTable } from 'container/QueryTable'; -import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; -import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { useSelector } from 'react-redux'; -import { AppState } from 'store/reducers'; -import { GlobalReducer } from 'types/reducer/globalTime'; - -export function LogsExplorerTable(): JSX.Element { - const { stagedQuery } = useQueryBuilder(); - - const { selectedTime } = useSelector( - (state) => state.globalTime, - ); - - const panelTypeParam = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); - - const { data, isFetching } = useGetQueryRange( - { - query: stagedQuery || initialQueriesMap.metrics, - graphType: panelTypeParam, - globalSelectedInterval: selectedTime, - selectedTime: 'GLOBAL_TIME', - }, - { - queryKey: [ - REACT_QUERY_KEY.GET_QUERY_RANGE, - selectedTime, - stagedQuery, - panelTypeParam, - ], - enabled: !!stagedQuery, - }, - ); - return ( - - ); -} diff --git a/frontend/src/container/LogsExplorerTable/index.ts b/frontend/src/container/LogsExplorerTable/index.ts deleted file mode 100644 index 4a9b82e594..0000000000 --- a/frontend/src/container/LogsExplorerTable/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LogsExplorerTable } from './LogsExplorerTable'; diff --git a/frontend/src/container/LogsExplorerTable/index.tsx b/frontend/src/container/LogsExplorerTable/index.tsx new file mode 100644 index 0000000000..40aefcf5e5 --- /dev/null +++ b/frontend/src/container/LogsExplorerTable/index.tsx @@ -0,0 +1,23 @@ +import { initialQueriesMap } from 'constants/queryBuilder'; +import { QueryTable } from 'container/QueryTable'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { memo } from 'react'; + +import { LogsExplorerTableProps } from './LogsExplorerTable.interfaces'; + +function LogsExplorerTable({ + isLoading, + data, +}: LogsExplorerTableProps): JSX.Element { + const { stagedQuery } = useQueryBuilder(); + + return ( + + ); +} + +export default memo(LogsExplorerTable); diff --git a/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx b/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx deleted file mode 100644 index 76765959b0..0000000000 --- a/frontend/src/container/LogsExplorerViews/LogsExplorerViews.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { TabsProps } from 'antd'; -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames'; -import { LogsExplorerTable } from 'container/LogsExplorerTable'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; -import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import useUrlQuery from 'hooks/useUrlQuery'; -import { useCallback, useEffect, useMemo } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; - -import { TabsStyled } from './LogsExplorerViews.styled'; - -export function LogsExplorerViews(): JSX.Element { - const location = useLocation(); - const urlQuery = useUrlQuery(); - const history = useHistory(); - const { currentQuery } = useQueryBuilder(); - - const panelTypeParams = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); - - const isMultipleQueries = useMemo( - () => - currentQuery.builder.queryData.length > 1 || - currentQuery.builder.queryFormulas.length > 0, - [currentQuery], - ); - - const isGroupByExist = useMemo(() => { - const groupByCount: number = currentQuery.builder.queryData.reduce( - (acc, query) => acc + query.groupBy.length, - 0, - ); - - return groupByCount > 0; - }, [currentQuery]); - - const tabsItems: TabsProps['items'] = useMemo( - () => [ - { - label: 'List View', - key: PANEL_TYPES.LIST, - disabled: isMultipleQueries || isGroupByExist, - }, - { label: 'TimeSeries', key: PANEL_TYPES.TIME_SERIES }, - { label: 'Table', key: PANEL_TYPES.TABLE, children: }, - ], - [isMultipleQueries, isGroupByExist], - ); - - const handleChangeView = useCallback( - (panelType: string) => { - urlQuery.set(PANEL_TYPES_QUERY, JSON.stringify(panelType) as GRAPH_TYPES); - const path = `${location.pathname}?${urlQuery}`; - - history.push(path); - }, - [history, location, urlQuery], - ); - - const currentTabKey = useMemo( - () => - Object.values(PANEL_TYPES).includes(panelTypeParams) - ? panelTypeParams - : PANEL_TYPES.LIST, - [panelTypeParams], - ); - - useEffect(() => { - const shouldChangeView = isMultipleQueries || isGroupByExist; - - if (panelTypeParams === 'list' && shouldChangeView) { - handleChangeView(PANEL_TYPES.TIME_SERIES); - } - }, [panelTypeParams, isMultipleQueries, isGroupByExist, handleChangeView]); - - return ( -
- -
- ); -} diff --git a/frontend/src/container/LogsExplorerViews/index.ts b/frontend/src/container/LogsExplorerViews/index.ts deleted file mode 100644 index 4a29ed0988..0000000000 --- a/frontend/src/container/LogsExplorerViews/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LogsExplorerViews } from './LogsExplorerViews'; diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx new file mode 100644 index 0000000000..f95c79d252 --- /dev/null +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -0,0 +1,124 @@ +import { TabsProps } from 'antd'; +import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; +import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import LogsExplorerList from 'container/LogsExplorerList'; +import LogsExplorerTable from 'container/LogsExplorerTable'; +import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { memo, useCallback, useEffect, useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { DataSource } from 'types/common/queryBuilder'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import { TabsStyled } from './LogsExplorerViews.styled'; + +function LogsExplorerViews(): JSX.Element { + const { + currentQuery, + stagedQuery, + panelType, + isEnabledQuery, + updateAllQueriesOperators, + redirectWithQueryBuilderData, + } = useQueryBuilder(); + + const { selectedTime } = useSelector( + (state) => state.globalTime, + ); + + const { data, isFetching } = useGetQueryRange( + { + query: stagedQuery || initialQueriesMap.metrics, + graphType: panelType || PANEL_TYPES.LIST, + globalSelectedInterval: selectedTime, + selectedTime: 'GLOBAL_TIME', + }, + { + queryKey: [REACT_QUERY_KEY.GET_QUERY_RANGE, selectedTime, stagedQuery], + enabled: isEnabledQuery, + }, + ); + + const isMultipleQueries = useMemo( + () => + currentQuery.builder.queryData.length > 1 || + currentQuery.builder.queryFormulas.length > 0, + [currentQuery], + ); + + const isGroupByExist = useMemo(() => { + const groupByCount: number = currentQuery.builder.queryData.reduce( + (acc, query) => acc + query.groupBy.length, + 0, + ); + + return groupByCount > 0; + }, [currentQuery]); + + const currentData = useMemo( + () => data?.payload.data.newResult.data.result || [], + [data], + ); + + const tabsItems: TabsProps['items'] = useMemo( + () => [ + { + label: 'List View', + key: PANEL_TYPES.LIST, + disabled: isMultipleQueries || isGroupByExist, + children: , + }, + { label: 'TimeSeries', key: PANEL_TYPES.TIME_SERIES }, + { + label: 'Table', + key: PANEL_TYPES.TABLE, + children: , + }, + ], + [isMultipleQueries, isGroupByExist, currentData, isFetching], + ); + + const handleChangeView = useCallback( + (newPanelType: string) => { + if (newPanelType === panelType) return; + + const query = updateAllQueriesOperators( + currentQuery, + newPanelType as GRAPH_TYPES, + DataSource.LOGS, + ); + + redirectWithQueryBuilderData(query, { [PANEL_TYPES_QUERY]: newPanelType }); + }, + [ + currentQuery, + panelType, + updateAllQueriesOperators, + redirectWithQueryBuilderData, + ], + ); + + useEffect(() => { + const shouldChangeView = isMultipleQueries || isGroupByExist; + + if (panelType === 'list' && shouldChangeView) { + handleChangeView(PANEL_TYPES.TIME_SERIES); + } + }, [panelType, isMultipleQueries, isGroupByExist, handleChangeView]); + + return ( +
+ +
+ ); +} + +export default memo(LogsExplorerViews); diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx index d3e52120b3..3f36301098 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx @@ -58,7 +58,7 @@ function QuerySection({ const { query } = selectedWidget; - useShareBuilderUrl({ defaultValue: query }); + useShareBuilderUrl(query); const handleStageQuery = useCallback( (updatedQuery: Query): void => { diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/QueryBuilder/QueryBuilder.tsx index dae8717920..8739d6281b 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.tsx +++ b/frontend/src/container/QueryBuilder/QueryBuilder.tsx @@ -15,26 +15,36 @@ import { ActionsWrapperStyled } from './QueryBuilder.styled'; export const QueryBuilder = memo(function QueryBuilder({ config, - panelType, + panelType: newPanelType, actions, }: QueryBuilderProps): JSX.Element { const { currentQuery, - setupInitialDataSource, addNewBuilderQuery, addNewFormula, - handleSetPanelType, + handleSetConfig, + panelType, + initialDataSource, } = useQueryBuilder(); - useEffect(() => { - if (config && config.queryVariant === 'static') { - setupInitialDataSource(config.initialDataSource); - } - }, [config, setupInitialDataSource]); + const currentDataSource = useMemo( + () => + (config && config.queryVariant === 'static' && config.initialDataSource) || + null, + [config], + ); useEffect(() => { - handleSetPanelType(panelType); - }, [handleSetPanelType, panelType]); + if (currentDataSource !== initialDataSource || newPanelType !== panelType) { + handleSetConfig(newPanelType, currentDataSource); + } + }, [ + handleSetConfig, + panelType, + initialDataSource, + currentDataSource, + newPanelType, + ]); const isDisabledQueryButton = useMemo( () => currentQuery.builder.queryData.length >= MAX_QUERIES, diff --git a/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts b/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts index 289e6c9f1a..309dda7a6c 100644 --- a/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts +++ b/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts @@ -1,6 +1,5 @@ -import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames'; +import { initialQueriesMap } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import useUrlQuery from 'hooks/useUrlQuery'; import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; import { UseQueryOptions, UseQueryResult } from 'react-query'; import { useSelector } from 'react-redux'; @@ -10,6 +9,7 @@ import { SuccessResponse } from 'types/api'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { GlobalReducer } from 'types/reducer/globalTime'; +import { useGetCompositeQueryParam } from './useGetCompositeQueryParam'; import { useGetQueryRange } from './useGetQueryRange'; export const useGetWidgetQueryRange = ( @@ -19,21 +19,19 @@ export const useGetWidgetQueryRange = ( }: Pick, options?: UseQueryOptions, Error>, ): UseQueryResult, Error> => { - const urlQuery = useUrlQuery(); - const { selectedTime: globalSelectedInterval } = useSelector< AppState, GlobalReducer >((state) => state.globalTime); - const compositeQuery = urlQuery.get(COMPOSITE_QUERY); + const compositeQuery = useGetCompositeQueryParam(); return useGetQueryRange( { graphType, selectedTime, globalSelectedInterval, - query: JSON.parse(compositeQuery || '{}'), + query: compositeQuery || initialQueriesMap.metrics, variables: getDashboardVariables(), }, { diff --git a/frontend/src/hooks/queryBuilder/useQueryOperations.ts b/frontend/src/hooks/queryBuilder/useQueryOperations.ts index b1d2f35c18..00cbdebcff 100644 --- a/frontend/src/hooks/queryBuilder/useQueryOperations.ts +++ b/frontend/src/hooks/queryBuilder/useQueryOperations.ts @@ -2,6 +2,7 @@ import { initialAutocompleteData, initialQueryBuilderFormValuesMap, mapOfFilters, + PANEL_TYPES, } from 'constants/queryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType'; @@ -78,7 +79,7 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => { (nextSource: DataSource): void => { const newOperators = getOperatorsBySourceAndPanelType({ dataSource: nextSource, - panelType, + panelType: panelType || PANEL_TYPES.TIME_SERIES, }); const entries = Object.entries( @@ -126,28 +127,13 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => { const initialOperators = getOperatorsBySourceAndPanelType({ dataSource, - panelType, + panelType: panelType || PANEL_TYPES.TIME_SERIES, }); if (JSON.stringify(operators) === JSON.stringify(initialOperators)) return; setOperators(initialOperators); - - const isCurrentOperatorAvailableInList = initialOperators - .map((operator) => operator.value) - .includes(aggregateOperator); - - if (!isCurrentOperatorAvailableInList) { - handleChangeOperator(initialOperators[0].value); - } - }, [ - dataSource, - initialDataSource, - panelType, - operators, - aggregateOperator, - handleChangeOperator, - ]); + }, [dataSource, initialDataSource, panelType, operators]); useEffect(() => { const additionalFilters = getNewListOfAdditionalFilters(dataSource); diff --git a/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts b/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts index 168e5af77a..b3ccc230e9 100644 --- a/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts +++ b/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts @@ -5,11 +5,9 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { useGetCompositeQueryParam } from './useGetCompositeQueryParam'; import { useQueryBuilder } from './useQueryBuilder'; -type UseShareBuilderUrlParams = { defaultValue: Query }; +export type UseShareBuilderUrlParams = { defaultValue: Query }; -export const useShareBuilderUrl = ({ - defaultValue, -}: UseShareBuilderUrlParams): void => { +export const useShareBuilderUrl = (defaultQuery: Query): void => { const { redirectWithQueryBuilderData, resetStagedQuery } = useQueryBuilder(); const urlQuery = useUrlQuery(); @@ -17,9 +15,9 @@ export const useShareBuilderUrl = ({ useEffect(() => { if (!compositeQuery) { - redirectWithQueryBuilderData(defaultValue); + redirectWithQueryBuilderData(defaultQuery); } - }, [defaultValue, urlQuery, redirectWithQueryBuilderData, compositeQuery]); + }, [defaultQuery, urlQuery, redirectWithQueryBuilderData, compositeQuery]); useEffect( () => (): void => { diff --git a/frontend/src/pages/LogsExplorer/index.tsx b/frontend/src/pages/LogsExplorer/index.tsx index fb8685717c..ef444f4a25 100644 --- a/frontend/src/pages/LogsExplorer/index.tsx +++ b/frontend/src/pages/LogsExplorer/index.tsx @@ -1,21 +1,32 @@ import { Button, Col, Row } from 'antd'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; -import { LogsExplorerChart } from 'container/LogsExplorerChart'; -import { LogsExplorerViews } from 'container/LogsExplorerViews'; +import LogsExplorerChart from 'container/LogsExplorerChart'; +import LogsExplorerViews from 'container/LogsExplorerViews'; import { QueryBuilder } from 'container/QueryBuilder'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; +import { useMemo } from 'react'; import { DataSource } from 'types/common/queryBuilder'; // ** Styles import { ButtonWrapperStyled, WrapperStyled } from './styles'; function LogsExporer(): JSX.Element { - const { handleRunQuery } = useQueryBuilder(); + const { handleRunQuery, updateAllQueriesOperators } = useQueryBuilder(); const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); - useShareBuilderUrl({ defaultValue: initialQueriesMap.logs }); + const defaultValue = useMemo( + () => + updateAllQueriesOperators( + initialQueriesMap.logs, + PANEL_TYPES.LIST, + DataSource.LOGS, + ), + [updateAllQueriesOperators], + ); + + useShareBuilderUrl(defaultValue); return ( diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index 57c2310d78..39f95bd5e2 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -37,7 +37,7 @@ function TracesExplorer(): JSX.Element { [redirectWithCurrentTab], ); - useShareBuilderUrl({ defaultValue: initialQueriesMap.traces }); + useShareBuilderUrl(initialQueriesMap.traces); useEffect(() => { if (currentUrlTab) return; diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index 2389ca01bc..0bfb4c38b9 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -52,11 +52,11 @@ export const QueryBuilderContext = createContext({ stagedQuery: initialQueriesMap.metrics, initialDataSource: null, panelType: PANEL_TYPES.TIME_SERIES, + isEnabledQuery: false, handleSetQueryData: () => {}, handleSetFormulaData: () => {}, handleSetQueryItemData: () => {}, - handleSetPanelType: () => {}, - setupInitialDataSource: () => {}, + handleSetConfig: () => {}, removeQueryBuilderEntityByIndex: () => {}, removeQueryTypeItemByIndex: () => {}, addNewBuilderQuery: () => {}, @@ -65,6 +65,7 @@ export const QueryBuilderContext = createContext({ redirectWithQueryBuilderData: () => {}, handleRunQuery: () => {}, resetStagedQuery: () => {}, + updateAllQueriesOperators: () => initialQueriesMap.metrics, }); export function QueryBuilderProvider({ @@ -75,75 +76,120 @@ export function QueryBuilderProvider({ const location = useLocation(); const compositeQueryParam = useGetCompositeQueryParam(); + const { queryType: queryTypeParam, ...queryState } = + compositeQueryParam || initialQueriesMap.metrics; const [initialDataSource, setInitialDataSource] = useState( null, ); - const [panelType, setPanelType] = useState( - PANEL_TYPES.TIME_SERIES, - ); + const [panelType, setPanelType] = useState(null); const [currentQuery, setCurrentQuery] = useState( - initialQueryState, + queryState || initialQueryState, ); const [stagedQuery, setStagedQuery] = useState(null); - const [queryType, setQueryType] = useState( - EQueryType.QUERY_BUILDER, + const [queryType, setQueryType] = useState(queryTypeParam); + + const getElementWithActualOperator = useCallback( + ( + queryData: IBuilderQuery, + dataSource: DataSource, + currentPanelType: GRAPH_TYPES, + ): IBuilderQuery => { + const initialOperators = getOperatorsBySourceAndPanelType({ + dataSource, + panelType: currentPanelType, + }); + + const isCurrentOperatorAvailableInList = initialOperators + .map((operator) => operator.value) + .includes(queryData.aggregateOperator); + + if (!isCurrentOperatorAvailableInList) { + return { ...queryData, aggregateOperator: initialOperators[0].value }; + } + + return queryData; + }, + [], ); - const initQueryBuilderData = useCallback( - (query: Query): void => { - const { queryType: newQueryType, ...queryState } = query; - + const prepareQueryBuilderData = useCallback( + (query: Query): Query => { const builder: QueryBuilderData = { - queryData: queryState.builder.queryData.map((item) => ({ + queryData: query.builder.queryData.map((item) => ({ ...initialQueryBuilderFormValuesMap[ initialDataSource || DataSource.METRICS ], ...item, })), - queryFormulas: queryState.builder.queryFormulas.map((item) => ({ + queryFormulas: query.builder.queryFormulas.map((item) => ({ ...initialFormulaBuilderFormValues, ...item, })), }; - const promql: IPromQLQuery[] = queryState.promql.map((item) => ({ + const setupedQueryData = builder.queryData.map((item) => { + const currentElement: IBuilderQuery = { + ...item, + groupBy: item.groupBy.map(({ id: _, ...item }) => ({ + ...item, + id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder), + })), + aggregateAttribute: { + ...item.aggregateAttribute, + id: createIdFromObjectFields( + item.aggregateAttribute, + baseAutoCompleteIdKeysOrder, + ), + }, + }; + + return currentElement; + }); + + const promql: IPromQLQuery[] = query.promql.map((item) => ({ ...initialQueryPromQLData, ...item, })); - const clickHouse: IClickHouseQuery[] = queryState.clickhouse_sql.map( - (item) => ({ - ...initialClickHouseData, - ...item, - }), - ); - - const type = newQueryType || EQueryType.QUERY_BUILDER; + const clickHouse: IClickHouseQuery[] = query.clickhouse_sql.map((item) => ({ + ...initialClickHouseData, + ...item, + })); const newQueryState: QueryState = { clickhouse_sql: clickHouse, promql, builder: { ...builder, - queryData: builder.queryData.map((q) => ({ - ...q, - groupBy: q.groupBy.map(({ id: _, ...item }) => ({ - ...item, - id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder), - })), - aggregateAttribute: { - ...q.aggregateAttribute, - id: createIdFromObjectFields( - q.aggregateAttribute, - baseAutoCompleteIdKeysOrder, - ), - }, - })), + queryData: setupedQueryData, }, + id: query.id, + }; + + const nextQuery: Query = { + ...newQueryState, + queryType: query.queryType, + }; + + return nextQuery; + }, + [initialDataSource], + ); + + const initQueryBuilderData = useCallback( + (query: Query): void => { + const { queryType: newQueryType, ...queryState } = prepareQueryBuilderData( + query, + ); + + const type = newQueryType || EQueryType.QUERY_BUILDER; + + const newQueryState: QueryState = { + ...queryState, id: queryState.id, }; @@ -153,7 +199,19 @@ export function QueryBuilderProvider({ setCurrentQuery(newQueryState); setQueryType(type); }, - [initialDataSource], + [prepareQueryBuilderData], + ); + + const updateAllQueriesOperators = useCallback( + (query: Query, panelType: GRAPH_TYPES, dataSource: DataSource): Query => { + const queryData = query.builder.queryData.map((item) => + getElementWithActualOperator(item, dataSource, panelType), + ); + + return { ...query, builder: { ...query.builder, queryData } }; + }, + + [getElementWithActualOperator], ); const removeQueryBuilderEntityByIndex = useCallback( @@ -161,11 +219,14 @@ export function QueryBuilderProvider({ setCurrentQuery((prevState) => { const currentArray: (IBuilderQuery | IBuilderFormula)[] = prevState.builder[type]; + + const filteredArray = currentArray.filter((_, i) => index !== i); + return { ...prevState, builder: { ...prevState.builder, - [type]: currentArray.filter((_, i) => index !== i), + [type]: filteredArray, }, }; }); @@ -199,20 +260,11 @@ export function QueryBuilderProvider({ existNames, sourceNames: alphabet, }), - ...(initialDataSource - ? { - dataSource: initialDataSource, - aggregateOperator: getOperatorsBySourceAndPanelType({ - dataSource: initialDataSource, - panelType, - })[0].value, - } - : {}), }; return newQuery; }, - [initialDataSource, panelType], + [initialDataSource], ); const createNewBuilderFormula = useCallback((formulas: IBuilderFormula[]) => { @@ -297,12 +349,6 @@ export function QueryBuilderProvider({ }); }, [createNewBuilderFormula]); - const setupInitialDataSource = useCallback( - (newInitialDataSource: DataSource | null) => - setInitialDataSource(newInitialDataSource), - [], - ); - const updateQueryBuilderData: ( arr: T[], index: number, @@ -377,29 +423,33 @@ export function QueryBuilderProvider({ [updateQueryBuilderData], ); - const handleSetPanelType = useCallback((newPanelType: GRAPH_TYPES) => { - setPanelType(newPanelType); - }, []); - const redirectWithQueryBuilderData = useCallback( (query: Partial, searchParams?: Record) => { + const queryType = + !query.queryType || !Object.values(EQueryType).includes(query.queryType) + ? EQueryType.QUERY_BUILDER + : query.queryType; + + const builder = + !query.builder || query.builder.queryData.length === 0 + ? initialQueryState.builder + : query.builder; + + const promql = + !query.promql || query.promql.length === 0 + ? initialQueryState.promql + : query.promql; + + const clickhouseSql = + !query.clickhouse_sql || query.clickhouse_sql.length === 0 + ? initialQueryState.clickhouse_sql + : query.clickhouse_sql; + const currentGeneratedQuery: Query = { - queryType: - !query.queryType || !Object.values(EQueryType).includes(query.queryType) - ? EQueryType.QUERY_BUILDER - : query.queryType, - builder: - !query.builder || query.builder.queryData.length === 0 - ? initialQueryState.builder - : query.builder, - promql: - !query.promql || query.promql.length === 0 - ? initialQueryState.promql - : query.promql, - clickhouse_sql: - !query.clickhouse_sql || query.clickhouse_sql.length === 0 - ? initialQueryState.clickhouse_sql - : query.clickhouse_sql, + queryType, + builder, + promql, + clickhouse_sql: clickhouseSql, id: uuid(), }; @@ -418,6 +468,14 @@ export function QueryBuilderProvider({ [history, location, urlQuery], ); + const handleSetConfig = useCallback( + (newPanelType: GRAPH_TYPES, dataSource: DataSource | null) => { + setPanelType(newPanelType); + setInitialDataSource(dataSource); + }, + [], + ); + const handleRunQuery = useCallback(() => { redirectWithQueryBuilderData({ ...currentQuery, queryType }); }, [redirectWithQueryBuilderData, currentQuery, queryType]); @@ -458,17 +516,22 @@ export function QueryBuilderProvider({ [currentQuery, queryType], ); + const isEnabledQuery = useMemo(() => !!stagedQuery && !!panelType, [ + stagedQuery, + panelType, + ]); + const contextValues: QueryBuilderContextType = useMemo( () => ({ currentQuery: query, stagedQuery, initialDataSource, panelType, + isEnabledQuery, handleSetQueryData, handleSetFormulaData, handleSetQueryItemData, - handleSetPanelType, - setupInitialDataSource, + handleSetConfig, removeQueryBuilderEntityByIndex, removeQueryTypeItemByIndex, addNewBuilderQuery, @@ -477,17 +540,18 @@ export function QueryBuilderProvider({ redirectWithQueryBuilderData, handleRunQuery, resetStagedQuery, + updateAllQueriesOperators, }), [ query, stagedQuery, initialDataSource, panelType, + isEnabledQuery, handleSetQueryData, handleSetFormulaData, handleSetQueryItemData, - handleSetPanelType, - setupInitialDataSource, + handleSetConfig, removeQueryBuilderEntityByIndex, removeQueryTypeItemByIndex, addNewBuilderQuery, @@ -496,6 +560,7 @@ export function QueryBuilderProvider({ redirectWithQueryBuilderData, handleRunQuery, resetStagedQuery, + updateAllQueriesOperators, ], ); diff --git a/frontend/src/types/api/widgets/getQuery.ts b/frontend/src/types/api/widgets/getQuery.ts index 7452b52dda..f97ffb59e2 100644 --- a/frontend/src/types/api/widgets/getQuery.ts +++ b/frontend/src/types/api/widgets/getQuery.ts @@ -1,8 +1,12 @@ +import { ILog } from '../logs/log'; + export interface PayloadProps { status: 'success' | 'error'; result: QueryData[]; } +type ListItem = { timestamp: string; data: Omit }; + export interface QueryData { metric: { [key: string]: string; @@ -20,7 +24,7 @@ export interface SeriesItem { } export interface QueryDataV3 { - list: null; + list: ListItem[] | null; queryName: string; legend?: string; series: SeriesItem[] | null; diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts index 16b6f8cd08..8681468a6f 100644 --- a/frontend/src/types/common/queryBuilder.ts +++ b/frontend/src/types/common/queryBuilder.ts @@ -156,7 +156,8 @@ export type QueryBuilderContextType = { currentQuery: Query; stagedQuery: Query | null; initialDataSource: DataSource | null; - panelType: GRAPH_TYPES; + panelType: GRAPH_TYPES | null; + isEnabledQuery: boolean; handleSetQueryData: (index: number, queryData: IBuilderQuery) => void; handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void; handleSetQueryItemData: ( @@ -164,8 +165,10 @@ export type QueryBuilderContextType = { type: EQueryType.PROM | EQueryType.CLICKHOUSE, newQueryData: IPromQLQuery | IClickHouseQuery, ) => void; - handleSetPanelType: (newPanelType: GRAPH_TYPES) => void; - setupInitialDataSource: (newInitialDataSource: DataSource | null) => void; + handleSetConfig: ( + newPanelType: GRAPH_TYPES, + dataSource: DataSource | null, + ) => void; removeQueryBuilderEntityByIndex: ( type: keyof QueryBuilderData, index: number, @@ -183,6 +186,11 @@ export type QueryBuilderContextType = { ) => void; handleRunQuery: () => void; resetStagedQuery: () => void; + updateAllQueriesOperators: ( + queryData: Query, + panelType: GRAPH_TYPES, + dataSource: DataSource, + ) => Query; }; export type QueryAdditionalFilter = { From 56402b0d40067e96494c2d4d03ebbdb721b25c25 Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Fri, 23 Jun 2023 21:39:59 +0300 Subject: [PATCH 06/29] feat: add the url pagination & update options menu (#2943) * feat: add dynamic table based on query * fix: group by repeating * fix: change view when groupBy exist in the list * fix: table scroll * feat: add the pagination and update options menu * feat: trace explorer is updated --------- Co-authored-by: Yevhen Shevchenko Co-authored-by: Nazarenko19 Co-authored-by: Palash Gupta --- frontend/src/constants/queryBuilder.ts | 1 + frontend/src/container/Controls/index.tsx | 58 +++--- frontend/src/container/LogControls/index.tsx | 5 +- .../ComponentsSlider/menuItems.ts | 8 +- .../OptionsMenu/AddColumnField/index.tsx | 30 +++- .../OptionsMenu/AddColumnField/styles.ts | 11 ++ .../OptionsMenu/FormatField/index.tsx | 2 +- .../OptionsMenu/MaxLinesField/index.tsx | 2 +- .../src/container/OptionsMenu/constants.ts | 9 + frontend/src/container/OptionsMenu/index.tsx | 26 +-- frontend/src/container/OptionsMenu/types.ts | 21 +++ .../container/OptionsMenu/useOptionsMenu.ts | 166 ++++++++++++++++++ frontend/src/container/OptionsMenu/utils.ts | 22 +++ .../TracesExplorer/Controls/index.tsx | 4 +- .../TracesExplorer/QuerySection/index.tsx | 5 +- frontend/src/hooks/queryPagination/config.ts | 8 + frontend/src/hooks/queryPagination/index.ts | 2 + frontend/src/hooks/queryPagination/types.ts | 4 + .../queryPagination/useQueryPagination.ts | 70 ++++++++ frontend/src/hooks/queryPagination/utils.ts | 12 ++ frontend/src/hooks/useUrlQueryData.ts | 44 +++++ frontend/src/pages/LogsExplorer/index.tsx | 4 +- .../src/pages/TracesExplorer/constants.ts | 6 - frontend/src/pages/TracesExplorer/index.tsx | 75 ++++---- frontend/src/pages/TracesExplorer/utils.tsx | 7 +- frontend/src/types/actions/logs.ts | 3 +- frontend/src/types/common/queryBuilder.ts | 3 +- frontend/src/types/reducer/logs.ts | 3 +- 28 files changed, 512 insertions(+), 99 deletions(-) create mode 100644 frontend/src/container/OptionsMenu/constants.ts create mode 100644 frontend/src/container/OptionsMenu/types.ts create mode 100644 frontend/src/container/OptionsMenu/useOptionsMenu.ts create mode 100644 frontend/src/container/OptionsMenu/utils.ts create mode 100644 frontend/src/hooks/queryPagination/config.ts create mode 100644 frontend/src/hooks/queryPagination/index.ts create mode 100644 frontend/src/hooks/queryPagination/types.ts create mode 100644 frontend/src/hooks/queryPagination/useQueryPagination.ts create mode 100644 frontend/src/hooks/queryPagination/utils.ts create mode 100644 frontend/src/hooks/useUrlQueryData.ts delete mode 100644 frontend/src/pages/TracesExplorer/constants.ts diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 0ac5de8030..2ac56af114 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -233,6 +233,7 @@ export const PANEL_TYPES: Record = { TABLE: 'table', LIST: 'list', EMPTY_WIDGET: 'EMPTY_WIDGET', + TRACE: 'trace', }; export type IQueryBuilderState = 'search'; diff --git a/frontend/src/container/Controls/index.tsx b/frontend/src/container/Controls/index.tsx index a9a656bfc8..e552bc9f28 100644 --- a/frontend/src/container/Controls/index.tsx +++ b/frontend/src/container/Controls/index.tsx @@ -1,33 +1,29 @@ import { LeftOutlined, RightOutlined } from '@ant-design/icons'; import { Button, Select } from 'antd'; +import { Pagination } from 'hooks/queryPagination'; import { memo, useMemo } from 'react'; import { defaultSelectStyle, ITEMS_PER_PAGE_OPTIONS } from './config'; import { Container } from './styles'; -interface ControlsProps { - count: number; - countPerPage: number; - isLoading: boolean; - handleNavigatePrevious: () => void; - handleNavigateNext: () => void; - handleCountItemsPerPageChange: (e: number) => void; -} - -function Controls(props: ControlsProps): JSX.Element | null { - const { - count, - isLoading, - countPerPage, - handleNavigatePrevious, - handleNavigateNext, - handleCountItemsPerPageChange, - } = props; - +function Controls({ + offset = 0, + isLoading, + totalCount, + countPerPage, + handleNavigatePrevious, + handleNavigateNext, + handleCountItemsPerPageChange, +}: ControlsProps): JSX.Element | null { const isNextAndPreviousDisabled = useMemo( - () => isLoading || countPerPage === 0 || count === 0 || count < countPerPage, - [isLoading, countPerPage, count], + () => isLoading || countPerPage < 0 || totalCount === 0, + [isLoading, countPerPage, totalCount], ); + const isPreviousDisabled = useMemo(() => offset <= 0, [offset]); + const isNextDisabled = useMemo(() => totalCount < countPerPage, [ + countPerPage, + totalCount, + ]); return ( @@ -35,7 +31,7 @@ function Controls(props: ControlsProps): JSX.Element | null { loading={isLoading} size="small" type="link" - disabled={isNextAndPreviousDisabled} + disabled={isPreviousDisabled || isNextAndPreviousDisabled} onClick={handleNavigatePrevious} > Previous @@ -44,12 +40,12 @@ function Controls(props: ControlsProps): JSX.Element | null { loading={isLoading} size="small" type="link" - disabled={isNextAndPreviousDisabled} + disabled={isNextDisabled || isNextAndPreviousDisabled} onClick={handleNavigateNext} > Next - onChange(Number(event.target.value))} - onKeyDown={handleKeyDown} + value={query.stepInterval} + onChange={onChangeHandler} + min={0} /> ); } diff --git a/frontend/src/container/TopNav/DateTimeSelection/index.tsx b/frontend/src/container/TopNav/DateTimeSelection/index.tsx index db4a0d8c42..2fc05ecca4 100644 --- a/frontend/src/container/TopNav/DateTimeSelection/index.tsx +++ b/frontend/src/container/TopNav/DateTimeSelection/index.tsx @@ -4,6 +4,9 @@ import getLocalStorageKey from 'api/browser/localstorage/get'; import setLocalStorageKey from 'api/browser/localstorage/set'; import { LOCALSTORAGE } from 'constants/localStorage'; import dayjs, { Dayjs } from 'dayjs'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval'; +import GetMinMax from 'lib/getMinMax'; import getTimeString from 'lib/getTimeString'; import { useCallback, useEffect, useState } from 'react'; import { connect, useSelector } from 'react-redux'; @@ -66,6 +69,8 @@ function DateTimeSelection({ false, ); + const { stagedQuery, initQueryBuilderData } = useQueryBuilder(); + const { maxTime, minTime, selectedTime } = useSelector< AppState, GlobalReducer @@ -174,6 +179,14 @@ function DateTimeSelection({ setRefreshButtonHidden(true); setCustomDTPickerVisible(true); } + + if (!stagedQuery) { + return; + } + + const { maxTime, minTime } = GetMinMax(value, getTime()); + + initQueryBuilderData(updateStepInterval(stagedQuery, maxTime, minTime)); }; const onRefreshHandler = (): void => { diff --git a/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts b/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts index 309dda7a6c..28bc07566f 100644 --- a/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts +++ b/frontend/src/hooks/queryBuilder/useGetWidgetQueryRange.ts @@ -9,8 +9,8 @@ import { SuccessResponse } from 'types/api'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { GlobalReducer } from 'types/reducer/globalTime'; -import { useGetCompositeQueryParam } from './useGetCompositeQueryParam'; import { useGetQueryRange } from './useGetQueryRange'; +import { useQueryBuilder } from './useQueryBuilder'; export const useGetWidgetQueryRange = ( { @@ -24,24 +24,24 @@ export const useGetWidgetQueryRange = ( GlobalReducer >((state) => state.globalTime); - const compositeQuery = useGetCompositeQueryParam(); + const { stagedQuery } = useQueryBuilder(); return useGetQueryRange( { graphType, selectedTime, globalSelectedInterval, - query: compositeQuery || initialQueriesMap.metrics, + query: stagedQuery || initialQueriesMap.metrics, variables: getDashboardVariables(), }, { - enabled: !!compositeQuery, + enabled: !!stagedQuery, retry: false, queryKey: [ REACT_QUERY_KEY.GET_QUERY_RANGE, selectedTime, globalSelectedInterval, - compositeQuery, + stagedQuery, ], ...options, }, diff --git a/frontend/src/hooks/queryBuilder/useStepInterval.ts b/frontend/src/hooks/queryBuilder/useStepInterval.ts new file mode 100644 index 0000000000..55dad54664 --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useStepInterval.ts @@ -0,0 +1,37 @@ +import getStep from 'lib/getStep'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { Widgets } from 'types/api/dashboard/getAll'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +export const updateStepInterval = ( + query: Widgets['query'], + maxTime: number, + minTime: number, +): Widgets['query'] => { + const stepInterval = getStep({ + start: minTime, + end: maxTime, + inputFormat: 'ns', + }); + + return { + ...query, + builder: { + ...query?.builder, + queryData: + query?.builder?.queryData?.map((item) => ({ + ...item, + stepInterval, + })) || [], + }, + }; +}; + +export const useStepInterval = (query: Widgets['query']): Widgets['query'] => { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + return updateStepInterval(query, maxTime, minTime); +}; diff --git a/frontend/src/lib/__tests__/getStep.test.ts b/frontend/src/lib/__tests__/getStep.test.ts index ad31573ebf..c799bfa9e0 100644 --- a/frontend/src/lib/__tests__/getStep.test.ts +++ b/frontend/src/lib/__tests__/getStep.test.ts @@ -39,7 +39,12 @@ describe('lib/getStep', () => { const startUnix = start.valueOf(); const endUnix = end.valueOf(); - const expectedStepSize = Math.floor(end.diff(start, 's') / MaxDataPoints); + let expectedStepSize = Math.max( + Math.floor(end.diff(start, 's') / MaxDataPoints), + DefaultStepSize, + ); + + expectedStepSize -= expectedStepSize % 60; expect( getStep({ diff --git a/frontend/src/lib/dashbaordVariables/getDashboardVariables.ts b/frontend/src/lib/dashbaordVariables/getDashboardVariables.ts index e9f4f1c6e1..aa870f9bbb 100644 --- a/frontend/src/lib/dashbaordVariables/getDashboardVariables.ts +++ b/frontend/src/lib/dashbaordVariables/getDashboardVariables.ts @@ -7,7 +7,7 @@ export const getDashboardVariables = (): Record => { globalTime, dashboards: { dashboards }, } = store.getState(); - const [selectedDashboard] = dashboards; + const [selectedDashboard] = dashboards || []; const { data: { variables = {} }, } = selectedDashboard; diff --git a/frontend/src/lib/getStep.test.ts b/frontend/src/lib/getStep.test.ts new file mode 100644 index 0000000000..a70057245f --- /dev/null +++ b/frontend/src/lib/getStep.test.ts @@ -0,0 +1,137 @@ +import dayjs from 'dayjs'; + +import getStep, { DefaultStepSize, MaxDataPoints } from './getStep'; + +describe('get dynamic step size', () => { + test('should return default step size if diffSec is less than MaxDataPoints', () => { + const start = dayjs().subtract(1, 'minute').valueOf(); + const end = dayjs().valueOf(); + + const step = getStep({ + start, + end, + inputFormat: 'ms', + }); + + expect(step).toBe(DefaultStepSize); + }); + + test('should return appropriate step size if diffSec is more than MaxDataPoints', () => { + const start = dayjs().subtract(4, 'hour').valueOf(); + const end = dayjs().valueOf(); + + const step = getStep({ + start, + end, + inputFormat: 'ms', + }); + + // the expected step size should be no less than DefaultStepSize + const diffSec = Math.abs(dayjs(end).diff(dayjs(start), 's')); + const expectedStep = Math.max( + Math.floor(diffSec / MaxDataPoints), + DefaultStepSize, + ); + + expect(step).toBe(expectedStep); + }); + + test('should correctly handle different input formats', () => { + const endSec = dayjs().unix(); + const startSec = endSec - 4 * 3600; // 4 hours earlier + + const stepSec = getStep({ + start: startSec, + end: endSec, + inputFormat: 's', + }); + + const diffSec = Math.abs(dayjs.unix(endSec).diff(dayjs.unix(startSec), 's')); + const expectedStep = Math.max( + Math.floor(diffSec / MaxDataPoints), + DefaultStepSize, + ); + + expect(stepSec).toBe(expectedStep); + + const startNs = startSec * 1e9; // convert to nanoseconds + const endNs = endSec * 1e9; // convert to nanoseconds + + const stepNs = getStep({ + start: startNs, + end: endNs, + inputFormat: 'ns', + }); + + expect(stepNs).toBe(expectedStep); // Expect the same result as 's' inputFormat + }); + + test('should throw an error for invalid input format', () => { + const start = dayjs().valueOf(); + const end = dayjs().valueOf(); + + expect(() => { + getStep({ + start, + end, + inputFormat: 'invalid' as never, + }); + }).toThrow('invalid format'); + }); + + test('should return DefaultStepSize when start and end are the same', () => { + const start = dayjs().valueOf(); + const end = start; // same as start + + const step = getStep({ + start, + end, + inputFormat: 'ms', + }); + + expect(step).toBe(DefaultStepSize); + }); + + test('should return DefaultStepSize if diffSec is exactly MaxDataPoints', () => { + const endMs = dayjs().valueOf(); + const startMs = endMs - MaxDataPoints * 1000; // exactly MaxDataPoints seconds earlier + + const step = getStep({ + start: startMs, + end: endMs, + inputFormat: 'ms', + }); + + expect(step).toBe(DefaultStepSize); // since calculated step size is less than DefaultStepSize, it should return DefaultStepSize + }); + + test('should return DefaultStepSize for future dates less than (MaxDataPoints * DefaultStepSize) seconds ahead', () => { + const start = dayjs().valueOf(); + const end = start + MaxDataPoints * DefaultStepSize * 1000 - 1; // just one millisecond less than (MaxDataPoints * DefaultStepSize) seconds ahead + + const step = getStep({ + start, + end, + inputFormat: 'ms', + }); + + expect(step).toBe(DefaultStepSize); + }); + + test('should handle string inputs correctly for a time range greater than (MaxDataPoints * DefaultStepSize) seconds', () => { + const endMs = dayjs().valueOf(); + const startMs = endMs - (MaxDataPoints * DefaultStepSize * 1000 + 1); // one millisecond more than (MaxDataPoints * DefaultStepSize) seconds earlier + + const step = getStep({ + start: startMs.toString(), + end: endMs.toString(), + inputFormat: 'ms', + }); + + const diffSec = Math.abs( + dayjs(Number(endMs)).diff(dayjs(Number(startMs)), 's'), + ); + + expect(step).toBe(Math.floor(diffSec / MaxDataPoints)); + }); +}); diff --git a/frontend/src/lib/getStep.ts b/frontend/src/lib/getStep.ts index 347f9dc332..0a7f4b043d 100644 --- a/frontend/src/lib/getStep.ts +++ b/frontend/src/lib/getStep.ts @@ -30,7 +30,7 @@ const convertToMs = ( }; export const DefaultStepSize = 60; -export const MaxDataPoints = 200; +export const MaxDataPoints = 300; /** * Returns relevant step size based on given start and end date. @@ -40,7 +40,13 @@ const getStep = ({ start, end, inputFormat = 'ms' }: GetStepInput): number => { const endDate = dayjs(convertToMs(Number(end), inputFormat)); const diffSec = Math.abs(endDate.diff(startDate, 's')); - return Math.max(Math.floor(diffSec / MaxDataPoints), DefaultStepSize); + let result = + Math.max(Math.floor(diffSec / MaxDataPoints), DefaultStepSize) || + DefaultStepSize; + + result -= result % 60; + + return result; }; export default getStep; diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index 0bfb4c38b9..56a8635633 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -16,6 +16,7 @@ import { import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam'; +import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval'; import useUrlQuery from 'hooks/useUrlQuery'; import { createIdFromObjectFields } from 'lib/createIdFromObjectFields'; import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName'; @@ -29,7 +30,9 @@ import { useMemo, useState, } from 'react'; +import { useSelector } from 'react-redux'; import { useHistory, useLocation } from 'react-router-dom'; +import { AppState } from 'store/reducers'; // ** Types import { IBuilderFormula, @@ -45,6 +48,7 @@ import { QueryBuilderContextType, QueryBuilderData, } from 'types/common/queryBuilder'; +import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuid } from 'uuid'; export const QueryBuilderContext = createContext({ @@ -66,6 +70,7 @@ export const QueryBuilderContext = createContext({ handleRunQuery: () => {}, resetStagedQuery: () => {}, updateAllQueriesOperators: () => initialQueriesMap.metrics, + initQueryBuilderData: () => {}, }); export function QueryBuilderProvider({ @@ -74,6 +79,9 @@ export function QueryBuilderProvider({ const urlQuery = useUrlQuery(); const history = useHistory(); const location = useLocation(); + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); const compositeQueryParam = useGetCompositeQueryParam(); const { queryType: queryTypeParam, ...queryState } = @@ -356,7 +364,6 @@ export function QueryBuilderProvider({ ) => T[] = useCallback( (arr, index, newQueryItem) => arr.map((item, idx) => (index === idx ? newQueryItem : item)), - [], ); @@ -465,7 +472,7 @@ export function QueryBuilderProvider({ history.push(generatedUrl); }, - [history, location, urlQuery], + [history, location.pathname, urlQuery], ); const handleSetConfig = useCallback( @@ -477,8 +484,24 @@ export function QueryBuilderProvider({ ); const handleRunQuery = useCallback(() => { - redirectWithQueryBuilderData({ ...currentQuery, queryType }); - }, [redirectWithQueryBuilderData, currentQuery, queryType]); + redirectWithQueryBuilderData({ + ...{ + ...currentQuery, + ...updateStepInterval( + { + builder: currentQuery.builder, + clickhouse_sql: currentQuery.clickhouse_sql, + promql: currentQuery.promql, + id: currentQuery.id, + queryType, + }, + maxTime, + minTime, + ), + }, + queryType, + }); + }, [currentQuery, queryType, maxTime, minTime, redirectWithQueryBuilderData]); const resetStagedQuery = useCallback(() => { setStagedQuery(null); @@ -541,6 +564,7 @@ export function QueryBuilderProvider({ handleRunQuery, resetStagedQuery, updateAllQueriesOperators, + initQueryBuilderData, }), [ query, @@ -561,6 +585,7 @@ export function QueryBuilderProvider({ handleRunQuery, resetStagedQuery, updateAllQueriesOperators, + initQueryBuilderData, ], ); diff --git a/frontend/src/store/actions/dashboard/getQueryResults.ts b/frontend/src/store/actions/dashboard/getQueryResults.ts index c139c232c3..137730770b 100644 --- a/frontend/src/store/actions/dashboard/getQueryResults.ts +++ b/frontend/src/store/actions/dashboard/getQueryResults.ts @@ -3,18 +3,19 @@ // @ts-nocheck import { getMetricsQueryRange } from 'api/metrics/getQueryRange'; +import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; import { Time } from 'container/TopNav/DateTimeSelection/config'; +import getStartEndRangeTime from 'lib/getStartEndRangeTime'; import getStep from 'lib/getStep'; +import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld'; import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi'; import { isEmpty } from 'lodash-es'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import store from 'store'; import { SuccessResponse } from 'types/api'; -import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; +import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; -import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld'; -import getStartEndRangeTime from 'lib/getStartEndRangeTime'; export async function GetMetricQueryRange({ query, @@ -89,7 +90,11 @@ export async function GetMetricQueryRange({ const response = await getMetricsQueryRange({ start: parseInt(start, 10) * 1e3, end: parseInt(end, 10) * 1e3, - step: getStep({ start, end, inputFormat: 'ms' }), + step: getStep({ + start: store.getState().globalTime.minTime, + end: store.getState().globalTime.maxTime, + inputFormat: 'ns', + }), variables, ...QueryPayload, ...params, diff --git a/frontend/src/store/actions/dashboard/saveDashboard.ts b/frontend/src/store/actions/dashboard/saveDashboard.ts index 107fecee25..09007415ef 100644 --- a/frontend/src/store/actions/dashboard/saveDashboard.ts +++ b/frontend/src/store/actions/dashboard/saveDashboard.ts @@ -4,6 +4,7 @@ import { AxiosError } from 'axios'; import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames'; import ROUTES from 'constants/routes'; import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; +import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval'; import history from 'lib/history'; import { Layout } from 'react-grid-layout'; import { generatePath } from 'react-router-dom'; @@ -88,9 +89,10 @@ export const SaveDashboard = ({ const allLayout = getAllLayout(); const params = new URLSearchParams(window.location.search); const compositeQuery = params.get(COMPOSITE_QUERY); + const { maxTime, minTime } = store.getState().globalTime; const query = compositeQuery - ? JSON.parse(compositeQuery) - : selectedWidget.query; + ? updateStepInterval(JSON.parse(compositeQuery), maxTime, minTime) + : updateStepInterval(selectedWidget.query, maxTime, minTime); const response = await updateDashboardApi({ data: { diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts index c76e81bef7..774ec5a65c 100644 --- a/frontend/src/types/common/queryBuilder.ts +++ b/frontend/src/types/common/queryBuilder.ts @@ -192,6 +192,7 @@ export type QueryBuilderContextType = { panelType: GRAPH_TYPES, dataSource: DataSource, ) => Query; + initQueryBuilderData: (query: Query) => void; }; export type QueryAdditionalFilter = { From 1eabacbaf487c4b07eb9ffdd1364c7779debee2f Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:55:20 +0300 Subject: [PATCH 08/29] feat: add the export panel to the traces explorer (#2983) * feat: add the export panel to the traces explorer * feat: onExport dashboard widget is updated * chore: made common hook useDashboard --------- Co-authored-by: Palash Gupta --- frontend/src/constants/query.ts | 2 + .../src/container/ExportPanel/ExportPanel.tsx | 34 ++++--- frontend/src/container/ExportPanel/index.tsx | 10 +- .../hooks/dashboard/useGetAllDashboard.tsx | 16 ++++ .../hooks/dashboard/useUpdateDashboard.tsx | 14 +++ frontend/src/hooks/dashboard/utils.ts | 37 ++++++++ frontend/src/pages/TracesExplorer/index.tsx | 93 +++++++++++++++---- frontend/src/pages/TracesExplorer/styles.ts | 5 + 8 files changed, 181 insertions(+), 30 deletions(-) create mode 100644 frontend/src/hooks/dashboard/useGetAllDashboard.tsx create mode 100644 frontend/src/hooks/dashboard/useUpdateDashboard.tsx create mode 100644 frontend/src/hooks/dashboard/utils.ts diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts index 35c1e2c2ca..ec8dd54e2b 100644 --- a/frontend/src/constants/query.ts +++ b/frontend/src/constants/query.ts @@ -12,4 +12,6 @@ export enum QueryParams { aggregationOption = 'aggregationOption', entity = 'entity', resourceAttributes = 'resourceAttribute', + graphType = 'graphType', + widgetId = 'widgetId', } diff --git a/frontend/src/container/ExportPanel/ExportPanel.tsx b/frontend/src/container/ExportPanel/ExportPanel.tsx index 2cd24e25d1..80092c75dc 100644 --- a/frontend/src/container/ExportPanel/ExportPanel.tsx +++ b/frontend/src/container/ExportPanel/ExportPanel.tsx @@ -1,12 +1,11 @@ import { Button, Typography } from 'antd'; import createDashboard from 'api/dashboard/create'; -import getAll from 'api/dashboard/getAll'; import axios from 'axios'; -import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard'; import { useNotifications } from 'hooks/useNotifications'; import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useMutation, useQuery } from 'react-query'; +import { useMutation } from 'react-query'; import { ExportPanelProps } from '.'; import { @@ -18,7 +17,7 @@ import { } from './styles'; import { getSelectOptions } from './utils'; -function ExportPanel({ onExport }: ExportPanelProps): JSX.Element { +function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element { const { notifications } = useNotifications(); const { t } = useTranslation(['dashboard']); @@ -26,16 +25,18 @@ function ExportPanel({ onExport }: ExportPanelProps): JSX.Element { null, ); - const { data, isLoading, refetch } = useQuery({ - queryFn: getAll, - queryKey: REACT_QUERY_KEY.GET_ALL_DASHBOARDS, - }); + const { + data, + isLoading: isAllDashboardsLoading, + refetch, + } = useGetAllDashboard(); const { mutate: createNewDashboard, isLoading: createDashboardLoading, } = useMutation(createDashboard, { - onSuccess: () => { + onSuccess: (data) => { + onExport(data?.payload || null); refetch(); }, onError: (error) => { @@ -73,6 +74,14 @@ function ExportPanel({ onExport }: ExportPanelProps): JSX.Element { }); }, [t, createNewDashboard]); + const isDashboardLoading = isAllDashboardsLoading || createDashboardLoading; + + const isDisabled = + isAllDashboardsLoading || + !options?.length || + !selectedDashboardId || + isLoading; + return ( Export Panel @@ -81,14 +90,15 @@ function ExportPanel({ onExport }: ExportPanelProps): JSX.Element { - + ); } +ExportPanel.defaultProps = { + isLoading: false, +}; + interface OnClickProps { key: string; } export interface ExportPanelProps { + isLoading?: boolean; onExport: (dashboard: Dashboard | null) => void; } diff --git a/frontend/src/hooks/dashboard/useGetAllDashboard.tsx b/frontend/src/hooks/dashboard/useGetAllDashboard.tsx new file mode 100644 index 0000000000..aced8e6e2e --- /dev/null +++ b/frontend/src/hooks/dashboard/useGetAllDashboard.tsx @@ -0,0 +1,16 @@ +import getAll from 'api/dashboard/getAll'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useQuery, UseQueryResult } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps } from 'types/api/dashboard/getAll'; + +export const useGetAllDashboard = (): DashboardProps => + useQuery({ + queryFn: getAll, + queryKey: REACT_QUERY_KEY.GET_ALL_DASHBOARDS, + }); + +type DashboardProps = UseQueryResult< + SuccessResponse | ErrorResponse, + unknown +>; diff --git a/frontend/src/hooks/dashboard/useUpdateDashboard.tsx b/frontend/src/hooks/dashboard/useUpdateDashboard.tsx new file mode 100644 index 0000000000..b4c34974dc --- /dev/null +++ b/frontend/src/hooks/dashboard/useUpdateDashboard.tsx @@ -0,0 +1,14 @@ +import update from 'api/dashboard/update'; +import { useMutation, UseMutationResult } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { Dashboard } from 'types/api/dashboard/getAll'; +import { Props } from 'types/api/dashboard/update'; + +export const useUpdateDashboard = (): UseUpdateDashboard => useMutation(update); + +type UseUpdateDashboard = UseMutationResult< + SuccessResponse | ErrorResponse, + unknown, + Props, + unknown +>; diff --git a/frontend/src/hooks/dashboard/utils.ts b/frontend/src/hooks/dashboard/utils.ts new file mode 100644 index 0000000000..ea2457daa8 --- /dev/null +++ b/frontend/src/hooks/dashboard/utils.ts @@ -0,0 +1,37 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { Dashboard } from 'types/api/dashboard/getAll'; +import { Query } from 'types/api/queryBuilder/queryBuilderData'; + +export const addEmptyWidgetInDashboardJSONWithQuery = ( + dashboard: Dashboard, + query: Query, +): Dashboard => ({ + ...dashboard, + data: { + ...dashboard.data, + layout: [ + { + i: 'empty', + w: 6, + x: 0, + h: 2, + y: 0, + }, + ...(dashboard?.data?.layout || []), + ], + widgets: [ + ...(dashboard?.data?.widgets || []), + { + id: 'empty', + query, + description: '', + isStacked: false, + nullZeroValues: '', + opacity: '', + title: '', + timePreferance: 'GLOBAL_TIME', + panelTypes: PANEL_TYPES.TIME_SERIES, + }, + ], + }, +}); diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index 137e3bfbf5..f1215bc1ce 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -1,28 +1,95 @@ import { Tabs } from 'antd'; +import axios from 'axios'; +import { QueryParams } from 'constants/query'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; -import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames'; +import { + COMPOSITE_QUERY, + PANEL_TYPES_QUERY, +} from 'constants/queryBuilderQueryNames'; +import ROUTES from 'constants/routes'; +import ExportPanel from 'container/ExportPanel'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import QuerySection from 'container/TracesExplorer/QuerySection'; +import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; +import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; +import { useNotifications } from 'hooks/useNotifications'; +import history from 'lib/history'; import { useCallback, useMemo } from 'react'; +import { generatePath } from 'react-router-dom'; +import { Dashboard } from 'types/api/dashboard/getAll'; import { DataSource } from 'types/common/queryBuilder'; -import { Container } from './styles'; +import { ActionsWrapper, Container } from './styles'; import { getTabsItems } from './utils'; function TracesExplorer(): JSX.Element { + const { notifications } = useNotifications(); const { + currentQuery, + stagedQuery, + panelType, updateAllQueriesOperators, redirectWithQueryBuilderData, - currentQuery, - panelType, } = useQueryBuilder(); const tabsItems = getTabsItems(); - const currentTab = panelType || PANEL_TYPES.TIME_SERIES; + const defaultQuery = useMemo( + () => + updateAllQueriesOperators( + initialQueriesMap.traces, + PANEL_TYPES.TIME_SERIES, + DataSource.TRACES, + ), + [updateAllQueriesOperators], + ); + + const exportDefaultQuery = useMemo( + () => + updateAllQueriesOperators( + stagedQuery || initialQueriesMap.traces, + PANEL_TYPES.TIME_SERIES, + DataSource.TRACES, + ), + [stagedQuery, updateAllQueriesOperators], + ); + + const { mutate: updateDashboard, isLoading } = useUpdateDashboard(); + + const handleExport = useCallback( + (dashboard: Dashboard | null): void => { + if (!dashboard) return; + + const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery( + dashboard, + exportDefaultQuery, + ); + + updateDashboard(updatedDashboard, { + onSuccess: (data) => { + const dashboardEditView = `${generatePath(ROUTES.DASHBOARD, { + dashboardId: data?.payload?.uuid, + })}/new?${QueryParams.graphType}=graph&${ + QueryParams.widgetId + }=empty&${COMPOSITE_QUERY}=${JSON.stringify(exportDefaultQuery)}`; + + history.push(dashboardEditView); + }, + onError: (error) => { + if (axios.isAxiosError(error)) { + notifications.error({ + message: error.message, + }); + } + }, + }); + }, + [exportDefaultQuery, notifications, updateDashboard], + ); + const handleTabChange = useCallback( (newPanelType: string): void => { if (panelType === newPanelType) return; @@ -43,23 +110,17 @@ function TracesExplorer(): JSX.Element { ], ); - const defaultValue = useMemo( - () => - updateAllQueriesOperators( - initialQueriesMap.traces, - PANEL_TYPES.TIME_SERIES, - DataSource.TRACES, - ), - [updateAllQueriesOperators], - ); - - useShareBuilderUrl(defaultValue); + useShareBuilderUrl(defaultQuery); return ( <> + + + + Date: Thu, 29 Jun 2023 09:41:49 +0100 Subject: [PATCH 09/29] feat: sort logs in ascending order (#2895) * feat: sort logs in ascending order Co-authored-by: gitstart Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com> Co-authored-by: RubensRafael * refactor: requested changes Co-authored-by: Nitesh Singh Co-authored-by: niteshsingh1357 Co-authored-by: RubensRafael * fix: lint Co-authored-by: niteshsingh1357 Co-authored-by: Nitesh Singh Co-authored-by: RubensRafael * chore: removed the magic string --------- Co-authored-by: gitstart Co-authored-by: gitstart Co-authored-by: Nitesh Singh Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com> Co-authored-by: Palash Gupta Co-authored-by: RubensRafael Co-authored-by: Vishal Sharma Co-authored-by: niteshsingh1357 --- frontend/src/constants/query.ts | 2 + frontend/src/container/LogControls/index.tsx | 3 ++ .../container/LogDetailedView/ActionItem.tsx | 7 ++-- .../src/container/LogsSearchFilter/index.tsx | 10 +++-- .../LogsSearchFilter/useSearchParser.ts | 4 +- frontend/src/pages/Logs/config.ts | 21 ++++++++++ frontend/src/pages/Logs/index.tsx | 38 +++++++++++++++++-- frontend/src/pages/Logs/utils.ts | 24 +++++++++++- frontend/src/store/reducers/logs.ts | 17 +++++++++ frontend/src/types/actions/logs.ts | 10 ++++- frontend/src/types/reducer/logs.ts | 2 + 11 files changed, 125 insertions(+), 13 deletions(-) diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts index ec8dd54e2b..7325252ca8 100644 --- a/frontend/src/constants/query.ts +++ b/frontend/src/constants/query.ts @@ -14,4 +14,6 @@ export enum QueryParams { resourceAttributes = 'resourceAttribute', graphType = 'graphType', widgetId = 'widgetId', + order = 'order', + q = 'q', } diff --git a/frontend/src/container/LogControls/index.tsx b/frontend/src/container/LogControls/index.tsx index e98a15e13d..d0dfe4f27b 100644 --- a/frontend/src/container/LogControls/index.tsx +++ b/frontend/src/container/LogControls/index.tsx @@ -7,6 +7,7 @@ import { getMinMax } from 'container/TopNav/AutoRefresh/config'; import dayjs from 'dayjs'; import { Pagination } from 'hooks/queryPagination'; import { FlatLogData } from 'lib/logs/flatLogData'; +import { OrderPreferenceItems } from 'pages/Logs/config'; import * as Papa from 'papaparse'; import { memo, useCallback, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -31,6 +32,7 @@ function LogControls(): JSX.Element | null { isLoading: isLogsLoading, isLoadingAggregate, logs, + order, } = useSelector((state) => state.logs); const globalTime = useSelector( (state) => state.globalTime, @@ -160,6 +162,7 @@ function LogControls(): JSX.Element | null { loading={isLoading} size="small" type="link" + disabled={order === OrderPreferenceItems.ASC} onClick={handleGoToLatest} > Go to latest diff --git a/frontend/src/container/LogDetailedView/ActionItem.tsx b/frontend/src/container/LogDetailedView/ActionItem.tsx index 28e429d7e2..2d2350a324 100644 --- a/frontend/src/container/LogDetailedView/ActionItem.tsx +++ b/frontend/src/container/LogDetailedView/ActionItem.tsx @@ -2,6 +2,7 @@ import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { Button, Col, Popover } from 'antd'; import getStep from 'lib/getStep'; import { generateFilterQuery } from 'lib/logs/generateFilterQuery'; +import { getIdConditions } from 'pages/Logs/utils'; import { memo, useMemo } from 'react'; import { connect, useDispatch, useSelector } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; @@ -45,6 +46,7 @@ function ActionItem({ idStart, liveTail, idEnd, + order, } = useSelector((store) => store.logs); const dispatch = useDispatch>(); @@ -72,11 +74,10 @@ function ActionItem({ q: updatedQueryString, limit: logLinesPerPage, orderBy: 'timestamp', - order: 'desc', + order, timestampStart: minTime, timestampEnd: maxTime, - ...(idStart ? { idGt: idStart } : {}), - ...(idEnd ? { idLt: idEnd } : {}), + ...getIdConditions(idStart, idEnd, order), }); getLogsAggregate({ timestampStart: minTime, diff --git a/frontend/src/container/LogsSearchFilter/index.tsx b/frontend/src/container/LogsSearchFilter/index.tsx index d3de26658b..4b857aa83b 100644 --- a/frontend/src/container/LogsSearchFilter/index.tsx +++ b/frontend/src/container/LogsSearchFilter/index.tsx @@ -2,6 +2,7 @@ import { Input, InputRef, Popover } from 'antd'; import useUrlQuery from 'hooks/useUrlQuery'; import getStep from 'lib/getStep'; import debounce from 'lodash-es/debounce'; +import { getIdConditions } from 'pages/Logs/utils'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { connect, useDispatch, useSelector } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; @@ -33,7 +34,7 @@ function SearchFilter({ const [searchText, setSearchText] = useState(queryString); const [showDropDown, setShowDropDown] = useState(false); const searchRef = useRef(null); - const { logLinesPerPage, idEnd, idStart, liveTail } = useSelector< + const { logLinesPerPage, idEnd, idStart, liveTail, order } = useSelector< AppState, ILogsReducer >((state) => state.logs); @@ -99,11 +100,10 @@ function SearchFilter({ q: customQuery, limit: logLinesPerPage, orderBy: 'timestamp', - order: 'desc', + order, timestampStart: minTime, timestampEnd: maxTime, - ...(idStart ? { idGt: idStart } : {}), - ...(idEnd ? { idLt: idEnd } : {}), + ...getIdConditions(idStart, idEnd, order), }); getLogsAggregate({ @@ -128,6 +128,7 @@ function SearchFilter({ logLinesPerPage, globalTime, getLogsFields, + order, ], ); @@ -160,6 +161,7 @@ function SearchFilter({ dispatch, globalTime.maxTime, globalTime.minTime, + order, ]); const onPopOverChange = useCallback( diff --git a/frontend/src/container/LogsSearchFilter/useSearchParser.ts b/frontend/src/container/LogsSearchFilter/useSearchParser.ts index 54b483f4e7..00da96856d 100644 --- a/frontend/src/container/LogsSearchFilter/useSearchParser.ts +++ b/frontend/src/container/LogsSearchFilter/useSearchParser.ts @@ -1,3 +1,4 @@ +import { QueryParams } from 'constants/query'; import { getMinMax } from 'container/TopNav/AutoRefresh/config'; import useUrlQuery from 'hooks/useUrlQuery'; import history from 'lib/history'; @@ -25,6 +26,7 @@ export function useSearchParser(): { const dispatch = useDispatch>(); const { searchFilter: { parsedQuery, queryString }, + order, } = useSelector((store) => store.logs); const urlQuery = useUrlQuery(); @@ -39,7 +41,7 @@ export function useSearchParser(): { (updatedQueryString: string) => { history.replace({ pathname: history.location.pathname, - search: `?q=${updatedQueryString}`, + search: `?${QueryParams.q}=${updatedQueryString}&${QueryParams.order}=${order}`, }); const globalTime = getMinMax(selectedTime, minTime, maxTime); diff --git a/frontend/src/pages/Logs/config.ts b/frontend/src/pages/Logs/config.ts index 60f46195bd..39a251cb05 100644 --- a/frontend/src/pages/Logs/config.ts +++ b/frontend/src/pages/Logs/config.ts @@ -25,3 +25,24 @@ export const logsOptions = ['raw', 'table']; export const defaultSelectStyle: CSSProperties = { minWidth: '6rem', }; + +export enum OrderPreferenceItems { + DESC = 'desc', + ASC = 'asc', +} + +export const orderItems: OrderPreference[] = [ + { + name: 'Descending', + enum: OrderPreferenceItems.DESC, + }, + { + name: 'Ascending', + enum: OrderPreferenceItems.ASC, + }, +]; + +export interface OrderPreference { + name: string; + enum: OrderPreferenceItems; +} diff --git a/frontend/src/pages/Logs/index.tsx b/frontend/src/pages/Logs/index.tsx index 76433a7c4d..4810d3e00b 100644 --- a/frontend/src/pages/Logs/index.tsx +++ b/frontend/src/pages/Logs/index.tsx @@ -1,4 +1,5 @@ import { Button, Col, Divider, Popover, Row, Select, Space } from 'antd'; +import { QueryParams } from 'constants/query'; import LogControls from 'container/LogControls'; import LogDetailedView from 'container/LogDetailedView'; import LogLiveTail from 'container/LogLiveTail'; @@ -6,20 +7,31 @@ import LogsAggregate from 'container/LogsAggregate'; import LogsFilters from 'container/LogsFilters'; import LogsSearchFilter from 'container/LogsSearchFilter'; import LogsTable from 'container/LogsTable'; +import history from 'lib/history'; import { useCallback, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; import { Dispatch } from 'redux'; +import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; -import { SET_DETAILED_LOG_DATA } from 'types/actions/logs'; +import { SET_DETAILED_LOG_DATA, SET_LOGS_ORDER } from 'types/actions/logs'; import { ILog } from 'types/api/logs/log'; +import { ILogsReducer } from 'types/reducer/logs'; -import { defaultSelectStyle, logsOptions } from './config'; +import { + defaultSelectStyle, + logsOptions, + orderItems, + OrderPreferenceItems, +} from './config'; import { useSelectedLogView } from './hooks'; import PopoverContent from './PopoverContent'; import SpaceContainer from './styles'; function Logs(): JSX.Element { const dispatch = useDispatch>(); + const { order } = useSelector((store) => store.logs); + const location = useLocation(); const showExpandedLog = useCallback( (logData: ILog) => { @@ -67,6 +79,16 @@ function Logs(): JSX.Element { [handleViewModeOptionChange], ); + const handleChangeOrder = (value: OrderPreferenceItems): void => { + dispatch({ + type: SET_LOGS_ORDER, + payload: value, + }); + const params = new URLSearchParams(location.search); + params.set(QueryParams.order, value); + history.push({ search: params.toString() }); + }; + return ( <> Format )} + + diff --git a/frontend/src/pages/Logs/utils.ts b/frontend/src/pages/Logs/utils.ts index 2d8f317a9a..8fffa3bb21 100644 --- a/frontend/src/pages/Logs/utils.ts +++ b/frontend/src/pages/Logs/utils.ts @@ -1,7 +1,29 @@ import { LogViewMode } from 'container/LogsTable'; -import { viewModeOptionList } from './config'; +import { OrderPreferenceItems, viewModeOptionList } from './config'; export const isLogViewMode = (value: unknown): value is LogViewMode => typeof value === 'string' && viewModeOptionList.some((option) => option.key === value); + +export const getIdConditions = ( + idStart: string, + idEnd: string, + order: OrderPreferenceItems, +): Record => { + const idConditions: Record = {}; + + if (idStart && order === OrderPreferenceItems.ASC) { + idConditions.idLt = idStart; + } else if (idStart) { + idConditions.idGt = idStart; + } + + if (idEnd && order === OrderPreferenceItems.ASC) { + idConditions.idGt = idEnd; + } else if (idEnd) { + idConditions.idLt = idEnd; + } + + return idConditions; +}; diff --git a/frontend/src/store/reducers/logs.ts b/frontend/src/store/reducers/logs.ts index 06c6b41b70..6572834fde 100644 --- a/frontend/src/store/reducers/logs.ts +++ b/frontend/src/store/reducers/logs.ts @@ -1,4 +1,5 @@ import { parseQuery } from 'lib/logql'; +import { OrderPreferenceItems } from 'pages/Logs/config'; import { ADD_SEARCH_FIELD_QUERY_STRING, FLUSH_LOGS, @@ -17,6 +18,7 @@ import { SET_LOG_LINES_PER_PAGE, SET_LOGS, SET_LOGS_AGGREGATE_SERIES, + SET_LOGS_ORDER, SET_SEARCH_QUERY_PARSED_PAYLOAD, SET_SEARCH_QUERY_STRING, SET_VIEW_MODE, @@ -49,6 +51,10 @@ const initialState: ILogsReducer = { liveTailStartRange: 15, selectedLogId: null, detailedLog: null, + order: + (new URLSearchParams(window.location.search).get( + 'order', + ) as ILogsReducer['order']) ?? OrderPreferenceItems.DESC, }; export const LogsReducer = ( @@ -129,6 +135,17 @@ export const LogsReducer = ( logs: logsData, }; } + + case SET_LOGS_ORDER: { + const order = action.payload; + return { + ...state, + order, + idStart: '', + idEnd: '', + }; + } + case SET_LOG_LINES_PER_PAGE: { return { ...state, diff --git a/frontend/src/types/actions/logs.ts b/frontend/src/types/actions/logs.ts index 15b2663a6f..fbaa14a345 100644 --- a/frontend/src/types/actions/logs.ts +++ b/frontend/src/types/actions/logs.ts @@ -1,6 +1,7 @@ import { LogViewMode } from 'container/LogsTable'; import { Pagination } from 'hooks/queryPagination'; import { ILogQLParsedQueryItem } from 'lib/logql/types'; +import { OrderPreferenceItems } from 'pages/Logs/config'; import { IField, IFieldMoveToSelected, IFields } from 'types/api/logs/fields'; import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { ILog } from 'types/api/logs/log'; @@ -34,6 +35,7 @@ export const SET_LINES_PER_ROW = 'SET_LINES_PER_ROW'; export const SET_VIEW_MODE = 'SET_VIEW_MODE'; export const UPDATE_SELECTED_FIELDS = 'LOGS_UPDATE_SELECTED_FIELDS'; export const UPDATE_INTERESTING_FIELDS = 'LOGS_UPDATE_INTERESTING_FIELDS'; +export const SET_LOGS_ORDER = 'SET_LOGS_ORDER'; export interface GetFields { type: typeof GET_FIELDS; @@ -141,6 +143,11 @@ export interface UpdateSelectedInterestFields { }; } +export interface SetLogsOrder { + type: typeof SET_LOGS_ORDER; + payload: OrderPreferenceItems; +} + export type LogsActions = | GetFields | SetFields @@ -164,4 +171,5 @@ export type LogsActions = | SetLiveTailStartTime | SetLinesPerRow | SetViewMode - | UpdateSelectedInterestFields; + | UpdateSelectedInterestFields + | SetLogsOrder; diff --git a/frontend/src/types/reducer/logs.ts b/frontend/src/types/reducer/logs.ts index 2a873122f4..52a216f62f 100644 --- a/frontend/src/types/reducer/logs.ts +++ b/frontend/src/types/reducer/logs.ts @@ -1,6 +1,7 @@ import { LogViewMode } from 'container/LogsTable'; import { Pagination } from 'hooks/queryPagination'; import { ILogQLParsedQueryItem } from 'lib/logql/types'; +import { OrderPreferenceItems } from 'pages/Logs/config'; import { IFields } from 'types/api/logs/fields'; import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { ILog } from 'types/api/logs/log'; @@ -25,6 +26,7 @@ export interface ILogsReducer { detailedLog: null | ILog; liveTail: TLogsLiveTailState; liveTailStartRange: number; // time in minutes + order: OrderPreferenceItems; } export default ILogsReducer; From 3464b2a59c8a31b709ffea1e3f09be16efd9b6f9 Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Thu, 29 Jun 2023 14:22:55 +0530 Subject: [PATCH 10/29] feat: added time series tab in logs explorer (#2982) --- .../src/container/LogsExplorerViews/index.tsx | 16 ++++-- .../TimeSeriesView/TimeSeriesView.tsx | 53 +++++++++++++++++++ .../TimeSeriesView/index.tsx | 52 ++++++------------ .../TimeSeriesView/styles.ts | 0 frontend/src/pages/TracesExplorer/utils.tsx | 5 +- 5 files changed, 86 insertions(+), 40 deletions(-) create mode 100644 frontend/src/container/TimeSeriesView/TimeSeriesView.tsx rename frontend/src/container/{TracesExplorer => }/TimeSeriesView/index.tsx (52%) rename frontend/src/container/{TracesExplorer => }/TimeSeriesView/styles.ts (100%) diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index f95c79d252..6229a3fe22 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -5,6 +5,7 @@ import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import LogsExplorerList from 'container/LogsExplorerList'; import LogsExplorerTable from 'container/LogsExplorerTable'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { memo, useCallback, useEffect, useMemo } from 'react'; @@ -29,12 +30,15 @@ function LogsExplorerViews(): JSX.Element { (state) => state.globalTime, ); - const { data, isFetching } = useGetQueryRange( + const { data, isFetching, isError } = useGetQueryRange( { query: stagedQuery || initialQueriesMap.metrics, graphType: panelType || PANEL_TYPES.LIST, globalSelectedInterval: selectedTime, selectedTime: 'GLOBAL_TIME', + params: { + dataSource: DataSource.LOGS, + }, }, { queryKey: [REACT_QUERY_KEY.GET_QUERY_RANGE, selectedTime, stagedQuery], @@ -71,14 +75,20 @@ function LogsExplorerViews(): JSX.Element { disabled: isMultipleQueries || isGroupByExist, children: , }, - { label: 'TimeSeries', key: PANEL_TYPES.TIME_SERIES }, + { + label: 'TimeSeries', + key: PANEL_TYPES.TIME_SERIES, + children: ( + + ), + }, { label: 'Table', key: PANEL_TYPES.TABLE, children: , }, ], - [isMultipleQueries, isGroupByExist, currentData, isFetching], + [isMultipleQueries, isGroupByExist, currentData, isFetching, data, isError], ); const handleChangeView = useCallback( diff --git a/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx b/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx new file mode 100644 index 0000000000..398823205f --- /dev/null +++ b/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx @@ -0,0 +1,53 @@ +import Graph from 'components/Graph'; +import Spinner from 'components/Spinner'; +import getChartData from 'lib/getChartData'; +import { useMemo } from 'react'; +import { SuccessResponse } from 'types/api'; +import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; + +import { Container, ErrorText } from './styles'; + +function TimeSeriesView({ + data, + isLoading, + isError, +}: TimeSeriesViewProps): JSX.Element { + const chartData = useMemo( + () => + getChartData({ + queryData: [ + { + queryData: data?.payload?.data?.result || [], + }, + ], + }), + [data], + ); + + return ( + + {isLoading && } + {isError && {data?.error || 'Something went wrong'}} + {!isLoading && !isError && ( + + )} + + ); +} + +interface TimeSeriesViewProps { + data?: SuccessResponse; + isLoading: boolean; + isError: boolean; +} + +TimeSeriesView.defaultProps = { + data: undefined, +}; + +export default TimeSeriesView; diff --git a/frontend/src/container/TracesExplorer/TimeSeriesView/index.tsx b/frontend/src/container/TimeSeriesView/index.tsx similarity index 52% rename from frontend/src/container/TracesExplorer/TimeSeriesView/index.tsx rename to frontend/src/container/TimeSeriesView/index.tsx index ee531d7f40..8728f0120c 100644 --- a/frontend/src/container/TracesExplorer/TimeSeriesView/index.tsx +++ b/frontend/src/container/TimeSeriesView/index.tsx @@ -1,18 +1,17 @@ -import Graph from 'components/Graph'; -import Spinner from 'components/Spinner'; import { initialQueriesMap } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import getChartData from 'lib/getChartData'; -import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; +import { DataSource } from 'types/common/queryBuilder'; import { GlobalReducer } from 'types/reducer/globalTime'; -import { Container, ErrorText } from './styles'; +import TimeSeriesView from './TimeSeriesView'; -function TimeSeriesView(): JSX.Element { +function TimeSeriesViewContainer({ + dataSource = DataSource.TRACES, +}: TimeSeriesViewProps): JSX.Element { const { stagedQuery } = useQueryBuilder(); const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector< @@ -22,12 +21,12 @@ function TimeSeriesView(): JSX.Element { const { data, isLoading, isError } = useGetQueryRange( { - query: stagedQuery || initialQueriesMap.traces, + query: stagedQuery || initialQueriesMap[dataSource], graphType: 'graph', selectedTime: 'GLOBAL_TIME', globalSelectedInterval: globalSelectedTime, params: { - dataSource: 'traces', + dataSource, }, }, { @@ -42,32 +41,15 @@ function TimeSeriesView(): JSX.Element { }, ); - const chartData = useMemo( - () => - getChartData({ - queryData: [ - { - queryData: data?.payload?.data?.result || [], - }, - ], - }), - [data], - ); - - return ( - - {isLoading && } - {isError && {data?.error || 'Something went wrong'}} - {!isLoading && !isError && ( - - )} - - ); + return ; } -export default TimeSeriesView; +interface TimeSeriesViewProps { + dataSource?: DataSource; +} + +TimeSeriesViewContainer.defaultProps = { + dataSource: DataSource.TRACES, +}; + +export default TimeSeriesViewContainer; diff --git a/frontend/src/container/TracesExplorer/TimeSeriesView/styles.ts b/frontend/src/container/TimeSeriesView/styles.ts similarity index 100% rename from frontend/src/container/TracesExplorer/TimeSeriesView/styles.ts rename to frontend/src/container/TimeSeriesView/styles.ts diff --git a/frontend/src/pages/TracesExplorer/utils.tsx b/frontend/src/pages/TracesExplorer/utils.tsx index 344d79072d..7c38207281 100644 --- a/frontend/src/pages/TracesExplorer/utils.tsx +++ b/frontend/src/pages/TracesExplorer/utils.tsx @@ -1,12 +1,13 @@ import { TabsProps } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; -import TimeSeriesView from 'container/TracesExplorer/TimeSeriesView'; +import TimeSeriesView from 'container/TimeSeriesView'; +import { DataSource } from 'types/common/queryBuilder'; export const getTabsItems = (): TabsProps['items'] => [ { label: 'Time Series', key: PANEL_TYPES.TIME_SERIES, - children: , + children: , }, { label: 'Traces', From 0f998a48450cf6ab3f56ed3a5e207de636743554 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:49:56 +0300 Subject: [PATCH 11/29] feat: add custom orderBy (#2975) * feat: add custom orderBy * chore: magic string is removed --------- Co-authored-by: Vishal Sharma Co-authored-by: Palash Gupta --- .../filters/OrderByFilter/OrderByFilter.tsx | 77 +++++++++++++++---- .../filters/OrderByFilter/config.ts | 4 + .../filters/OrderByFilter/utils.ts | 16 +++- 3 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 frontend/src/container/QueryBuilder/filters/OrderByFilter/config.ts diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx index 13a8baad33..96187ed6f6 100644 --- a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx @@ -1,6 +1,8 @@ import { Select, Spin } from 'antd'; import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; import { QueryBuilderKeys } from 'constants/queryBuilder'; +import { IOption } from 'hooks/useResourceAttribute/types'; +import { uniqWith } from 'lodash-es'; import * as Papa from 'papaparse'; import { useCallback, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; @@ -9,6 +11,7 @@ import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder'; import { selectStyle } from '../QueryBuilderSearch/config'; import { getRemoveOrderFromValue } from '../QueryBuilderSearch/utils'; +import { FILTERS } from './config'; import { OrderByFilterProps } from './OrderByFilter.interfaces'; import { checkIfKeyPresent, @@ -22,7 +25,7 @@ export function OrderByFilter({ onChange, }: OrderByFilterProps): JSX.Element { const [searchText, setSearchText] = useState(''); - const [selectedValue, setSelectedValue] = useState([]); + const [selectedValue, setSelectedValue] = useState([]); const { data, isFetching } = useQuery( [QueryBuilderKeys.GET_AGGREGATE_KEYS, searchText], @@ -55,23 +58,41 @@ export function OrderByFilter({ .flat() .concat([ { - label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) asc`, - value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}asc`, + label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.ASC}`, + value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}${FILTERS.ASC}`, }, { - label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) desc`, - value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}desc`, + label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.DESC}`, + value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}${FILTERS.DESC}`, }, ]), [query.aggregateAttribute.key, query.aggregateOperator, query.groupBy], ); + const customValue: IOption[] = useMemo(() => { + if (!searchText) return []; + + return [ + { + label: `${searchText} ${FILTERS.ASC}`, + value: `${searchText}${orderByValueDelimiter}${FILTERS.ASC}`, + }, + { + label: `${searchText} ${FILTERS.DESC}`, + value: `${searchText}${orderByValueDelimiter}${FILTERS.DESC}`, + }, + ]; + }, [searchText]); + const optionsData = useMemo(() => { const options = query.aggregateOperator === MetricAggregateOperator.NOOP ? noAggregationOptions : aggregationOptions; - return options.filter( + + const resultOption = [...customValue, ...options]; + + return resultOption.filter( (option) => !getLabelFromValue(selectedValue).includes( getRemoveOrderFromValue(option.value), @@ -79,30 +100,58 @@ export function OrderByFilter({ ); }, [ aggregationOptions, + customValue, noAggregationOptions, query.aggregateOperator, selectedValue, ]); - const handleChange = (values: string[]): void => { - setSelectedValue(values); - const orderByValues: OrderByPayload[] = values.map((item) => { - const match = Papa.parse(item, { delimiter: '|' }); + const getUniqValues = useCallback((values: IOption[]): IOption[] => { + const modifiedValues = values.map((item) => { + const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter }); + if (!match) return { label: item.label, value: item.value }; + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars + const [_, order] = match.data.flat() as string[]; + if (order) return { label: item.label, value: item.value }; + + return { + label: `${item.value} ${FILTERS.ASC}`, + value: `${item.value}${orderByValueDelimiter}${FILTERS.ASC}`, + }; + }); + + return uniqWith( + modifiedValues, + (current, next) => + getRemoveOrderFromValue(current.value) === + getRemoveOrderFromValue(next.value), + ); + }, []); + + const handleChange = (values: IOption[]): void => { + const result = getUniqValues(values); + + setSelectedValue(result); + const orderByValues: OrderByPayload[] = result.map((item) => { + const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter }); + if (match) { const [columnName, order] = match.data.flat() as string[]; return { columnName: checkIfKeyPresent(columnName, query.aggregateAttribute.key) ? '#SIGNOZ_VALUE' : columnName, - order, + order: order ?? 'asc', }; } return { - columnName: item, - order: '', + columnName: item.value, + order: 'asc', }; }); + + setSearchText(''); onChange(orderByValues); }; @@ -126,6 +175,8 @@ export function OrderByFilter({ showSearch disabled={isMetricsDataSource && isDisabledSelect} showArrow={false} + value={selectedValue} + labelInValue filterOption={false} options={optionsData} notFoundContent={isFetching ? : null} diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/config.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/config.ts new file mode 100644 index 0000000000..9e4b0c9a1b --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/config.ts @@ -0,0 +1,4 @@ +export const FILTERS = { + ASC: 'asc', + DESC: 'desc', +}; diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts index 9907e5b88f..540674dec2 100644 --- a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts @@ -2,9 +2,18 @@ import { IOption } from 'hooks/useResourceAttribute/types'; import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; import * as Papa from 'papaparse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData'; export const orderByValueDelimiter = '|'; +export const transformToOrderByStringValues = ( + orderBy: OrderByPayload[], +): IOption[] => + orderBy.map((item) => ({ + label: `${item.columnName} ${item.order}`, + value: `${item.columnName}${orderByValueDelimiter}${item.order}`, + })); + export function mapLabelValuePairs( arr: BaseAutocompleteData[], ): Array[] { @@ -28,14 +37,15 @@ export function mapLabelValuePairs( }); } -export function getLabelFromValue(arr: string[]): string[] { +export function getLabelFromValue(arr: IOption[]): string[] { return arr.flat().map((item) => { - const match = Papa.parse(item, { delimiter: orderByValueDelimiter }); + const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter }); if (match) { const [key] = match.data as string[]; return key[0]; } - return item; + + return item.value; }); } From 20687d5184b25653f5682586f86475915edbfb9f Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Fri, 30 Jun 2023 06:58:22 +0530 Subject: [PATCH 12/29] feat: ability to configure noisy top level operations to discard (#2978) --- ee/query-service/app/api/api.go | 2 + ee/query-service/app/server.go | 18 ++++-- ee/query-service/main.go | 14 +++-- .../app/clickhouseReader/reader.go | 13 +++-- pkg/query-service/app/http_handler.go | 9 ++- pkg/query-service/app/server.go | 16 +++++- pkg/query-service/interfaces/interface.go | 6 +- pkg/query-service/main.go | 14 +++-- pkg/query-service/model/config.go | 57 +++++++++++++++++++ 9 files changed, 119 insertions(+), 30 deletions(-) create mode 100644 pkg/query-service/model/config.go diff --git a/ee/query-service/app/api/api.go b/ee/query-service/app/api/api.go index 42410b65e7..df1220d80c 100644 --- a/ee/query-service/app/api/api.go +++ b/ee/query-service/app/api/api.go @@ -16,6 +16,7 @@ import ( type APIHandlerOptions struct { DataConnector interfaces.DataConnector + SkipConfig *basemodel.SkipConfig AppDao dao.ModelDao RulesManager *rules.Manager FeatureFlags baseint.FeatureLookup @@ -32,6 +33,7 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) { baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{ Reader: opts.DataConnector, + SkipConfig: opts.SkipConfig, AppDao: opts.AppDao, RuleManager: opts.RulesManager, FeatureFlags: opts.FeatureFlags}) diff --git a/ee/query-service/app/server.go b/ee/query-service/app/server.go index 942ed24ced..ec2895acd8 100644 --- a/ee/query-service/app/server.go +++ b/ee/query-service/app/server.go @@ -49,9 +49,10 @@ import ( const AppDbEngine = "sqlite" type ServerOptions struct { - PromConfigPath string - HTTPHostPort string - PrivateHostPort string + PromConfigPath string + SkipTopLvlOpsPath string + HTTPHostPort string + PrivateHostPort string // alert specific params DisableRules bool RuleRepoURL string @@ -119,7 +120,15 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) { go qb.Start(readerReady) reader = qb } else { - return nil, fmt.Errorf("Storage type: %s is not supported in query service", storage) + return nil, fmt.Errorf("storage type: %s is not supported in query service", storage) + } + skipConfig := &basemodel.SkipConfig{} + if serverOptions.SkipTopLvlOpsPath != "" { + // read skip config + skipConfig, err = basemodel.ReadSkipConfig(serverOptions.SkipTopLvlOpsPath) + if err != nil { + return nil, err + } } <-readerReady @@ -160,6 +169,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) { apiOpts := api.APIHandlerOptions{ DataConnector: reader, + SkipConfig: skipConfig, AppDao: modelDao, RulesManager: rm, FeatureFlags: lm, diff --git a/ee/query-service/main.go b/ee/query-service/main.go index 6d38fb9f65..67cbde2151 100644 --- a/ee/query-service/main.go +++ b/ee/query-service/main.go @@ -74,7 +74,7 @@ func initZapLog(enableQueryServiceLogOTLPExport bool) *zap.Logger { } func main() { - var promConfigPath string + var promConfigPath, skipTopLvlOpsPath string // disables rule execution but allows change to the rule definition var disableRules bool @@ -85,6 +85,7 @@ func main() { var enableQueryServiceLogOTLPExport bool flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)") + flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)") flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)") flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)") flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)") @@ -98,11 +99,12 @@ func main() { version.PrintVersion() serverOptions := &app.ServerOptions{ - HTTPHostPort: baseconst.HTTPHostPort, - PromConfigPath: promConfigPath, - PrivateHostPort: baseconst.PrivateHostPort, - DisableRules: disableRules, - RuleRepoURL: ruleRepoURL, + HTTPHostPort: baseconst.HTTPHostPort, + PromConfigPath: promConfigPath, + SkipTopLvlOpsPath: skipTopLvlOpsPath, + PrivateHostPort: baseconst.PrivateHostPort, + DisableRules: disableRules, + RuleRepoURL: ruleRepoURL, } // Read the jwt secret key diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index fb59650982..7237046e7d 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -716,7 +716,7 @@ func (r *ClickHouseReader) GetServicesList(ctx context.Context) (*[]string, erro return &services, nil } -func (r *ClickHouseReader) GetTopLevelOperations(ctx context.Context) (*map[string][]string, *model.ApiError) { +func (r *ClickHouseReader) GetTopLevelOperations(ctx context.Context, skipConfig *model.SkipConfig) (*map[string][]string, *model.ApiError) { operations := map[string][]string{} query := fmt.Sprintf(`SELECT DISTINCT name, serviceName FROM %s.%s`, r.TraceDB, r.topLevelOperationsTable) @@ -737,18 +737,21 @@ func (r *ClickHouseReader) GetTopLevelOperations(ctx context.Context) (*map[stri if _, ok := operations[serviceName]; !ok { operations[serviceName] = []string{} } + if skipConfig.ShouldSkip(serviceName, name) { + continue + } operations[serviceName] = append(operations[serviceName], name) } return &operations, nil } -func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.GetServicesParams) (*[]model.ServiceItem, *model.ApiError) { +func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.GetServicesParams, skipConfig *model.SkipConfig) (*[]model.ServiceItem, *model.ApiError) { if r.indexTable == "" { return nil, &model.ApiError{Typ: model.ErrorExec, Err: ErrNoIndexTable} } - topLevelOps, apiErr := r.GetTopLevelOperations(ctx) + topLevelOps, apiErr := r.GetTopLevelOperations(ctx, skipConfig) if apiErr != nil { return nil, apiErr } @@ -839,9 +842,9 @@ func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.G return &serviceItems, nil } -func (r *ClickHouseReader) GetServiceOverview(ctx context.Context, queryParams *model.GetServiceOverviewParams) (*[]model.ServiceOverviewItem, *model.ApiError) { +func (r *ClickHouseReader) GetServiceOverview(ctx context.Context, queryParams *model.GetServiceOverviewParams, skipConfig *model.SkipConfig) (*[]model.ServiceOverviewItem, *model.ApiError) { - topLevelOps, apiErr := r.GetTopLevelOperations(ctx) + topLevelOps, apiErr := r.GetTopLevelOperations(ctx, skipConfig) if apiErr != nil { return nil, apiErr } diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 4eb48d1064..3025000db3 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -63,6 +63,7 @@ type APIHandler struct { basePath string apiPrefix string reader interfaces.Reader + skipConfig *model.SkipConfig appDao dao.ModelDao alertManager am.Manager ruleManager *rules.Manager @@ -81,6 +82,7 @@ type APIHandlerOpts struct { // business data reader e.g. clickhouse Reader interfaces.Reader + SkipConfig *model.SkipConfig // dao layer to perform crud on app objects like dashboard, alerts etc AppDao dao.ModelDao @@ -102,6 +104,7 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) { aH := &APIHandler{ reader: opts.Reader, appDao: opts.AppDao, + skipConfig: opts.SkipConfig, alertManager: alertManager, ruleManager: opts.RuleManager, featureFlags: opts.FeatureFlags, @@ -1316,7 +1319,7 @@ func (aH *APIHandler) getServiceOverview(w http.ResponseWriter, r *http.Request) return } - result, apiErr := aH.reader.GetServiceOverview(r.Context(), query) + result, apiErr := aH.reader.GetServiceOverview(r.Context(), query, aH.skipConfig) if apiErr != nil && aH.HandleError(w, apiErr.Err, http.StatusInternalServerError) { return } @@ -1327,7 +1330,7 @@ func (aH *APIHandler) getServiceOverview(w http.ResponseWriter, r *http.Request) func (aH *APIHandler) getServicesTopLevelOps(w http.ResponseWriter, r *http.Request) { - result, apiErr := aH.reader.GetTopLevelOperations(r.Context()) + result, apiErr := aH.reader.GetTopLevelOperations(r.Context(), aH.skipConfig) if apiErr != nil { RespondError(w, apiErr, nil) return @@ -1343,7 +1346,7 @@ func (aH *APIHandler) getServices(w http.ResponseWriter, r *http.Request) { return } - result, apiErr := aH.reader.GetServices(r.Context(), query) + result, apiErr := aH.reader.GetServices(r.Context(), query, aH.skipConfig) if apiErr != nil && aH.HandleError(w, apiErr.Err, http.StatusInternalServerError) { return } diff --git a/pkg/query-service/app/server.go b/pkg/query-service/app/server.go index 10a172e000..293d3f8753 100644 --- a/pkg/query-service/app/server.go +++ b/pkg/query-service/app/server.go @@ -41,9 +41,10 @@ import ( ) type ServerOptions struct { - PromConfigPath string - HTTPHostPort string - PrivateHostPort string + PromConfigPath string + SkipTopLvlOpsPath string + HTTPHostPort string + PrivateHostPort string // alert specific params DisableRules bool RuleRepoURL string @@ -105,6 +106,14 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) { } else { return nil, fmt.Errorf("Storage type: %s is not supported in query service", storage) } + var skipConfig *model.SkipConfig + if serverOptions.SkipTopLvlOpsPath != "" { + // read skip config + skipConfig, err = model.ReadSkipConfig(serverOptions.SkipTopLvlOpsPath) + if err != nil { + return nil, err + } + } <-readerReady rm, err := makeRulesManager(serverOptions.PromConfigPath, constants.GetAlertManagerApiPrefix(), serverOptions.RuleRepoURL, localDB, reader, serverOptions.DisableRules, fm) @@ -115,6 +124,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) { telemetry.GetInstance().SetReader(reader) apiHandler, err := NewAPIHandler(APIHandlerOpts{ Reader: reader, + SkipConfig: skipConfig, AppDao: dao.DB(), RuleManager: rm, FeatureFlags: fm, diff --git a/pkg/query-service/interfaces/interface.go b/pkg/query-service/interfaces/interface.go index c4d9bbdbb6..b6a8015fc0 100644 --- a/pkg/query-service/interfaces/interface.go +++ b/pkg/query-service/interfaces/interface.go @@ -21,9 +21,9 @@ type Reader interface { GetInstantQueryMetricsResult(ctx context.Context, query *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError) GetQueryRangeResult(ctx context.Context, query *model.QueryRangeParams) (*promql.Result, *stats.QueryStats, *model.ApiError) - GetServiceOverview(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceOverviewItem, *model.ApiError) - GetTopLevelOperations(ctx context.Context) (*map[string][]string, *model.ApiError) - GetServices(ctx context.Context, query *model.GetServicesParams) (*[]model.ServiceItem, *model.ApiError) + GetServiceOverview(ctx context.Context, query *model.GetServiceOverviewParams, skipConfig *model.SkipConfig) (*[]model.ServiceOverviewItem, *model.ApiError) + GetTopLevelOperations(ctx context.Context, skipConfig *model.SkipConfig) (*map[string][]string, *model.ApiError) + GetServices(ctx context.Context, query *model.GetServicesParams, skipConfig *model.SkipConfig) (*[]model.ServiceItem, *model.ApiError) GetTopOperations(ctx context.Context, query *model.GetTopOperationsParams) (*[]model.TopOperationsItem, *model.ApiError) GetUsage(ctx context.Context, query *model.GetUsageParams) (*[]model.UsageItem, error) GetServicesList(ctx context.Context) (*[]string, error) diff --git a/pkg/query-service/main.go b/pkg/query-service/main.go index 10bfe67306..9d769a0940 100644 --- a/pkg/query-service/main.go +++ b/pkg/query-service/main.go @@ -26,7 +26,7 @@ func initZapLog() *zap.Logger { } func main() { - var promConfigPath string + var promConfigPath, skipTopLvlOpsPath string // disables rule execution but allows change to the rule definition var disableRules bool @@ -35,6 +35,7 @@ func main() { var ruleRepoURL string flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)") + flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)") flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)") flag.StringVar(&ruleRepoURL, "rules.repo-url", constants.AlertHelpPage, "(host address used to build rule link in alert messages)") flag.Parse() @@ -47,11 +48,12 @@ func main() { version.PrintVersion() serverOptions := &app.ServerOptions{ - HTTPHostPort: constants.HTTPHostPort, - PromConfigPath: promConfigPath, - PrivateHostPort: constants.PrivateHostPort, - DisableRules: disableRules, - RuleRepoURL: ruleRepoURL, + HTTPHostPort: constants.HTTPHostPort, + PromConfigPath: promConfigPath, + SkipTopLvlOpsPath: skipTopLvlOpsPath, + PrivateHostPort: constants.PrivateHostPort, + DisableRules: disableRules, + RuleRepoURL: ruleRepoURL, } // Read the jwt secret key diff --git a/pkg/query-service/model/config.go b/pkg/query-service/model/config.go new file mode 100644 index 0000000000..d1d23385ad --- /dev/null +++ b/pkg/query-service/model/config.go @@ -0,0 +1,57 @@ +package model + +import ( + "os" + + "gopkg.in/yaml.v2" +) + +type SkipConfig struct { + Services []ServiceSkipConfig `yaml:"services"` +} + +type ServiceSkipConfig struct { + Name string `yaml:"name"` + Operations []string `yaml:"operations"` +} + +func (s *SkipConfig) ShouldSkip(serviceName, name string) bool { + for _, service := range s.Services { + if service.Name == serviceName { + for _, operation := range service.Operations { + if name == operation { + return true + } + } + } + } + return false +} + +func ReadYaml(path string, v interface{}) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + decoder := yaml.NewDecoder(f) + err = decoder.Decode(v) + if err != nil { + return err + } + return nil +} + +func ReadSkipConfig(path string) (*SkipConfig, error) { + if path == "" { + return &SkipConfig{}, nil + } + + skipConfig := &SkipConfig{} + err := ReadYaml(path, skipConfig) + if err != nil { + return nil, err + } + return skipConfig, nil +} From 709bfda0ccf7a45f94c301e24fe0ecbc816ce11b Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Fri, 30 Jun 2023 10:55:45 +0530 Subject: [PATCH 13/29] fix: trace column attributes (#3000) --- .../app/clickhouseReader/reader.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index 7237046e7d..cf7ed5503c 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -4299,6 +4299,8 @@ func (r *ClickHouseReader) GetTraceAggregateAttributes(ctx context.Context, req if err := rows.Scan(&tagKey, &tagType, &dataType, &isColumn); err != nil { return nil, fmt.Errorf("error while scanning rows: %s", err.Error()) } + // TODO: Remove this once the column name are updated in the table + tagKey = tempHandleFixedColumns(tagKey) key := v3.AttributeKey{ Key: tagKey, DataType: v3.AttributeKeyDataType(dataType), @@ -4338,6 +4340,8 @@ func (r *ClickHouseReader) GetTraceAttributeKeys(ctx context.Context, req *v3.Fi if err := rows.Scan(&tagKey, &tagType, &dataType, &isColumn); err != nil { return nil, fmt.Errorf("error while scanning rows: %s", err.Error()) } + // TODO: Remove this once the column name are updated in the table + tagKey = tempHandleFixedColumns(tagKey) key := v3.AttributeKey{ Key: tagKey, DataType: v3.AttributeKeyDataType(dataType), @@ -4349,6 +4353,19 @@ func (r *ClickHouseReader) GetTraceAttributeKeys(ctx context.Context, req *v3.Fi return &response, nil } +// tempHandleFixedColumns is a temporary function to handle the fixed columns whose name has been changed in AttributeKeys Table +func tempHandleFixedColumns(tagKey string) string { + switch { + case tagKey == "traceId": + tagKey = "traceID" + case tagKey == "spanId": + tagKey = "spanID" + case tagKey == "parentSpanId": + tagKey = "parentSpanID" + } + return tagKey +} + func (r *ClickHouseReader) GetTraceAttributeValues(ctx context.Context, req *v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) { var query string From cda37b99b435489a0217aa58ebc954b9f1765f35 Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Fri, 30 Jun 2023 11:18:12 +0530 Subject: [PATCH 14/29] feat: create alerts is updated from trace explorer (#2995) --- .../CreateAlertRule/SelectAlertType/config.ts | 27 +++++++++ .../CreateAlertRule/SelectAlertType/index.tsx | 55 ++++++------------- .../CreateAlertRule/SelectAlertType/types.ts | 7 +++ .../src/container/CreateAlertRule/config.ts | 8 +++ .../src/container/CreateAlertRule/index.tsx | 20 ++++++- frontend/src/container/ExportPanel/index.tsx | 31 ++++++++++- .../GridGraphLayout/WidgetHeader/index.tsx | 2 +- .../container/ListAlertRules/ListAlert.tsx | 4 +- .../NewDashboard/ComponentsSlider/index.tsx | 4 +- .../queryBuilder/useGetCompositeQueryParam.ts | 11 +++- frontend/src/pages/TracesExplorer/index.tsx | 10 +++- frontend/src/providers/QueryBuilder.tsx | 5 +- .../store/actions/dashboard/saveDashboard.ts | 6 +- 13 files changed, 139 insertions(+), 51 deletions(-) create mode 100644 frontend/src/container/CreateAlertRule/SelectAlertType/config.ts create mode 100644 frontend/src/container/CreateAlertRule/SelectAlertType/types.ts create mode 100644 frontend/src/container/CreateAlertRule/config.ts diff --git a/frontend/src/container/CreateAlertRule/SelectAlertType/config.ts b/frontend/src/container/CreateAlertRule/SelectAlertType/config.ts new file mode 100644 index 0000000000..c973684e67 --- /dev/null +++ b/frontend/src/container/CreateAlertRule/SelectAlertType/config.ts @@ -0,0 +1,27 @@ +import { TFunction } from 'i18next'; +import { AlertTypes } from 'types/api/alerts/alertTypes'; + +import { OptionType } from './types'; + +export const getOptionList = (t: TFunction): OptionType[] => [ + { + title: t('metric_based_alert'), + selection: AlertTypes.METRICS_BASED_ALERT, + description: t('metric_based_alert_desc'), + }, + { + title: t('log_based_alert'), + selection: AlertTypes.LOGS_BASED_ALERT, + description: t('log_based_alert_desc'), + }, + { + title: t('traces_based_alert'), + selection: AlertTypes.TRACES_BASED_ALERT, + description: t('traces_based_alert_desc'), + }, + { + title: t('exceptions_based_alert'), + selection: AlertTypes.EXCEPTIONS_BASED_ALERT, + description: t('exceptions_based_alert_desc'), + }, +]; diff --git a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx index 8385c8462e..65a5914fca 100644 --- a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx +++ b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx @@ -1,61 +1,40 @@ import { Row } from 'antd'; +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { AlertTypes } from 'types/api/alerts/alertTypes'; +import { getOptionList } from './config'; import { AlertTypeCard, SelectTypeContainer } from './styles'; - -interface OptionType { - title: string; - selection: AlertTypes; - description: string; -} +import { OptionType } from './types'; function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element { const { t } = useTranslation(['alerts']); - const renderOptions = (): JSX.Element => { - const optionList: OptionType[] = [ - { - title: t('metric_based_alert'), - selection: AlertTypes.METRICS_BASED_ALERT, - description: t('metric_based_alert_desc'), - }, - { - title: t('log_based_alert'), - selection: AlertTypes.LOGS_BASED_ALERT, - description: t('log_based_alert_desc'), - }, - { - title: t('traces_based_alert'), - selection: AlertTypes.TRACES_BASED_ALERT, - description: t('traces_based_alert_desc'), - }, - { - title: t('exceptions_based_alert'), - selection: AlertTypes.EXCEPTIONS_BASED_ALERT, - description: t('exceptions_based_alert_desc'), - }, - ]; - return ( + const optionList = getOptionList(t); + + const renderOptions = useMemo( + () => ( <> - {optionList.map((o: OptionType) => ( + {optionList.map((option: OptionType) => ( { - onSelect(o.selection); + onSelect(option.selection); }} > - {o.description} + {option.description} ))} - ); - }; + ), + [onSelect, optionList], + ); + return (

{t('choose_alert_type')}

- {renderOptions()} + {renderOptions}
); } diff --git a/frontend/src/container/CreateAlertRule/SelectAlertType/types.ts b/frontend/src/container/CreateAlertRule/SelectAlertType/types.ts new file mode 100644 index 0000000000..670f5a2708 --- /dev/null +++ b/frontend/src/container/CreateAlertRule/SelectAlertType/types.ts @@ -0,0 +1,7 @@ +import { AlertTypes } from 'types/api/alerts/alertTypes'; + +export interface OptionType { + title: string; + selection: AlertTypes; + description: string; +} diff --git a/frontend/src/container/CreateAlertRule/config.ts b/frontend/src/container/CreateAlertRule/config.ts new file mode 100644 index 0000000000..fe52bb12bf --- /dev/null +++ b/frontend/src/container/CreateAlertRule/config.ts @@ -0,0 +1,8 @@ +import { AlertTypes } from 'types/api/alerts/alertTypes'; +import { DataSource } from 'types/common/queryBuilder'; + +export const ALERT_TYPE_VS_SOURCE_MAPPING = { + [DataSource.LOGS]: AlertTypes.LOGS_BASED_ALERT, + [DataSource.METRICS]: AlertTypes.METRICS_BASED_ALERT, + [DataSource.TRACES]: AlertTypes.TRACES_BASED_ALERT, +}; diff --git a/frontend/src/container/CreateAlertRule/index.tsx b/frontend/src/container/CreateAlertRule/index.tsx index 40145d324e..9ce1634d13 100644 --- a/frontend/src/container/CreateAlertRule/index.tsx +++ b/frontend/src/container/CreateAlertRule/index.tsx @@ -1,9 +1,11 @@ import { Form, Row } from 'antd'; import FormAlertRules from 'container/FormAlertRules'; -import { useState } from 'react'; +import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam'; +import { useEffect, useState } from 'react'; import { AlertTypes } from 'types/api/alerts/alertTypes'; import { AlertDef } from 'types/api/alerts/def'; +import { ALERT_TYPE_VS_SOURCE_MAPPING } from './config'; import { alertDefaults, exceptionAlertDefaults, @@ -17,6 +19,9 @@ function CreateRules(): JSX.Element { const [alertType, setAlertType] = useState( AlertTypes.METRICS_BASED_ALERT, ); + + const compositeQuery = useGetCompositeQueryParam(); + const [formInstance] = Form.useForm(); const onSelectType = (typ: AlertTypes): void => { @@ -36,6 +41,19 @@ function CreateRules(): JSX.Element { } }; + useEffect(() => { + if (!compositeQuery) { + return; + } + const dataSource = compositeQuery?.builder?.queryData[0]?.dataSource; + + const alertType = ALERT_TYPE_VS_SOURCE_MAPPING[dataSource]; + + if (alertType) { + onSelectType(alertType); + } + }, [compositeQuery]); + if (!initValues) { return ( diff --git a/frontend/src/container/ExportPanel/index.tsx b/frontend/src/container/ExportPanel/index.tsx index 2a95bfd9ab..087b17fe72 100644 --- a/frontend/src/container/ExportPanel/index.tsx +++ b/frontend/src/container/ExportPanel/index.tsx @@ -1,24 +1,44 @@ import { Button, Dropdown, MenuProps, Modal } from 'antd'; +import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; import { useCallback, useMemo, useState } from 'react'; import { Dashboard } from 'types/api/dashboard/getAll'; +import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { MENU_KEY, MENU_LABEL } from './config'; import ExportPanelContainer from './ExportPanel'; -function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element { +function ExportPanel({ + isLoading, + onExport, + query, +}: ExportPanelProps): JSX.Element { const [isExport, setIsExport] = useState(false); const onModalToggle = useCallback((value: boolean) => { setIsExport(value); }, []); + const onCreateAlertsHandler = useCallback(() => { + history.push( + `${ROUTES.ALERTS_NEW}?${COMPOSITE_QUERY}=${encodeURIComponent( + JSON.stringify(query), + )}`, + ); + }, [query]); + const onMenuClickHandler: MenuProps['onClick'] = useCallback( (e: OnClickProps) => { if (e.key === MENU_KEY.EXPORT) { onModalToggle(true); } + + if (e.key === MENU_KEY.CREATE_ALERTS) { + onCreateAlertsHandler(); + } }, - [onModalToggle], + [onModalToggle, onCreateAlertsHandler], ); const menu: MenuProps = useMemo( @@ -54,7 +74,11 @@ function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element { open={isExport} centered > - + ); @@ -71,6 +95,7 @@ interface OnClickProps { export interface ExportPanelProps { isLoading?: boolean; onExport: (dashboard: Dashboard | null) => void; + query: Query | null; } export default ExportPanel; diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx index 30390c2824..a68d48ef8d 100644 --- a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx +++ b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx @@ -64,7 +64,7 @@ function WidgetHeader({ history.push( `${window.location.pathname}/new?widgetId=${widgetId}&graphType=${ widget.panelTypes - }&${COMPOSITE_QUERY}=${JSON.stringify(widget.query)}`, + }&${COMPOSITE_QUERY}=${encodeURIComponent(JSON.stringify(widget.query))}`, ); }, [widget.id, widget.panelTypes, widget.query]); diff --git a/frontend/src/container/ListAlertRules/ListAlert.tsx b/frontend/src/container/ListAlertRules/ListAlert.tsx index c1204eb79b..76843be306 100644 --- a/frontend/src/container/ListAlertRules/ListAlert.tsx +++ b/frontend/src/container/ListAlertRules/ListAlert.tsx @@ -77,8 +77,8 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { history.push( `${ ROUTES.EDIT_ALERTS - }?ruleId=${record.id.toString()}&${COMPOSITE_QUERY}=${JSON.stringify( - compositeQuery, + }?ruleId=${record.id.toString()}&${COMPOSITE_QUERY}=${encodeURIComponent( + JSON.stringify(compositeQuery), )}`, ); }) diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx index b20c4388f8..f490f1f23e 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx +++ b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx @@ -47,7 +47,9 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element { history.push( `${history.location.pathname}/new?graphType=${name}&widgetId=${ emptyLayout.i - }&${COMPOSITE_QUERY}=${JSON.stringify(initialQueriesMap.metrics)}`, + }&${COMPOSITE_QUERY}=${encodeURIComponent( + JSON.stringify(initialQueriesMap.metrics), + )}`, ); } catch (error) { notifications.error({ diff --git a/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts b/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts index 4477a9fbf7..894167815b 100644 --- a/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts +++ b/frontend/src/hooks/queryBuilder/useGetCompositeQueryParam.ts @@ -8,7 +8,16 @@ export const useGetCompositeQueryParam = (): Query | null => { return useMemo(() => { const compositeQuery = urlQuery.get(COMPOSITE_QUERY); + let parsedCompositeQuery: Query | null = null; - return compositeQuery ? JSON.parse(compositeQuery) : null; + try { + if (!compositeQuery) return null; + + parsedCompositeQuery = JSON.parse(decodeURIComponent(compositeQuery)); + } catch (e) { + parsedCompositeQuery = null; + } + + return parsedCompositeQuery; }, [urlQuery]); }; diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index f1215bc1ce..22f7a4f153 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -74,7 +74,9 @@ function TracesExplorer(): JSX.Element { dashboardId: data?.payload?.uuid, })}/new?${QueryParams.graphType}=graph&${ QueryParams.widgetId - }=empty&${COMPOSITE_QUERY}=${JSON.stringify(exportDefaultQuery)}`; + }=empty&${COMPOSITE_QUERY}=${encodeURIComponent( + JSON.stringify(exportDefaultQuery), + )}`; history.push(dashboardEditView); }, @@ -118,7 +120,11 @@ function TracesExplorer(): JSX.Element { - + diff --git a/frontend/src/store/actions/dashboard/saveDashboard.ts b/frontend/src/store/actions/dashboard/saveDashboard.ts index 09007415ef..14e62fedac 100644 --- a/frontend/src/store/actions/dashboard/saveDashboard.ts +++ b/frontend/src/store/actions/dashboard/saveDashboard.ts @@ -91,7 +91,11 @@ export const SaveDashboard = ({ const compositeQuery = params.get(COMPOSITE_QUERY); const { maxTime, minTime } = store.getState().globalTime; const query = compositeQuery - ? updateStepInterval(JSON.parse(compositeQuery), maxTime, minTime) + ? updateStepInterval( + JSON.parse(decodeURIComponent(compositeQuery)), + maxTime, + minTime, + ) : updateStepInterval(selectedWidget.query, maxTime, minTime); const response = await updateDashboardApi({ From 20e71ec08ae6a5cdc621e2807e9728fb78c1e8b6 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Mon, 3 Jul 2023 13:30:37 +0530 Subject: [PATCH 15/29] fix: add support for {max/min/avg} of rate (#2951) --- .../app/metrics/v3/query_builder.go | 8 ++- .../app/metrics/v3/query_builder_test.go | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/pkg/query-service/app/metrics/v3/query_builder.go b/pkg/query-service/app/metrics/v3/query_builder.go index dc5aadb618..780fbd3b89 100644 --- a/pkg/query-service/app/metrics/v3/query_builder.go +++ b/pkg/query-service/app/metrics/v3/query_builder.go @@ -37,6 +37,10 @@ var aggregateOperatorToSQLFunc = map[v3.AggregateOperator]string{ v3.AggregateOperatorRateAvg: "avg", v3.AggregateOperatorRateMax: "max", v3.AggregateOperatorRateMin: "min", + v3.AggregateOperatorSumRate: "sum", + v3.AggregateOperatorAvgRate: "avg", + v3.AggregateOperatorMaxRate: "max", + v3.AggregateOperatorMinRate: "min", } // See https://github.com/SigNoz/signoz/issues/2151#issuecomment-1467249056 @@ -212,7 +216,7 @@ func buildMetricQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str query = fmt.Sprintf(query, "labels as fullLabels,", subQuery) return query, nil - case v3.AggregateOperatorSumRate: + case v3.AggregateOperatorSumRate, v3.AggregateOperatorAvgRate, v3.AggregateOperatorMaxRate, v3.AggregateOperatorMinRate: rateGroupBy := "fingerprint, " + groupBy rateGroupTags := "fingerprint, " + groupTags rateOrderBy := "fingerprint, " + orderBy @@ -222,7 +226,7 @@ func buildMetricQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str ) // labels will be same so any should be fine query := `SELECT %s ts, ` + rateWithoutNegative + `as value FROM(%s) WHERE isNaN(value) = 0` query = fmt.Sprintf(query, groupTags, subQuery) - query = fmt.Sprintf(`SELECT %s ts, sum(value) as value FROM (%s) GROUP BY %s ORDER BY %s ts`, groupTags, query, groupBy, orderBy) + query = fmt.Sprintf(`SELECT %s ts, %s(value) as value FROM (%s) GROUP BY %s ORDER BY %s ts`, groupTags, aggregateOperatorToSQLFunc[mq.AggregateOperator], query, groupBy, orderBy) return query, nil case v3.AggregateOperatorRateSum, diff --git a/pkg/query-service/app/metrics/v3/query_builder_test.go b/pkg/query-service/app/metrics/v3/query_builder_test.go index ff35ed15f2..b07a788855 100644 --- a/pkg/query-service/app/metrics/v3/query_builder_test.go +++ b/pkg/query-service/app/metrics/v3/query_builder_test.go @@ -234,3 +234,56 @@ func TestBuildQueryOperators(t *testing.T) { }) } } + +func TestBuildQueryXRate(t *testing.T) { + t.Run("TestBuildQueryXRate", func(t *testing.T) { + + tmpl := `SELECT ts, %s(value) as value FROM (SELECT ts, if (runningDifference(value) < 0 OR runningDifference(ts) < 0, nan, runningDifference(value)/runningDifference(ts))as value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 0 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 GLOBAL INNER JOIN (SELECT fingerprint FROM signoz_metrics.distributed_time_series_v2 WHERE metric_name = 'name') as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND timestamp_ms >= 1650991982000 AND timestamp_ms <= 1651078382000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WHERE isNaN(value) = 0) GROUP BY ts ORDER BY ts` + + cases := []struct { + aggregateOperator v3.AggregateOperator + expectedQuery string + }{ + { + aggregateOperator: v3.AggregateOperatorAvgRate, + expectedQuery: fmt.Sprintf(tmpl, aggregateOperatorToSQLFunc[v3.AggregateOperatorAvgRate]), + }, + { + aggregateOperator: v3.AggregateOperatorMaxRate, + expectedQuery: fmt.Sprintf(tmpl, aggregateOperatorToSQLFunc[v3.AggregateOperatorMaxRate]), + }, + { + aggregateOperator: v3.AggregateOperatorMinRate, + expectedQuery: fmt.Sprintf(tmpl, aggregateOperatorToSQLFunc[v3.AggregateOperatorMinRate]), + }, + { + aggregateOperator: v3.AggregateOperatorSumRate, + expectedQuery: fmt.Sprintf(tmpl, aggregateOperatorToSQLFunc[v3.AggregateOperatorSumRate]), + }, + } + + for _, c := range cases { + + q := &v3.QueryRangeParamsV3{ + Start: 1650991982000, + End: 1651078382000, + Step: 60, + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "name"}, + AggregateOperator: c.aggregateOperator, + Expression: "A", + }, + }, + QueryType: v3.QueryTypeBuilder, + PanelType: v3.PanelTypeGraph, + }, + } + query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"]) + require.NoError(t, err) + require.Equal(t, query, c.expectedQuery) + } + }) +} From 78da014b5258df03fc347299c18d8f1827bf91b0 Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Tue, 4 Jul 2023 07:20:12 +0300 Subject: [PATCH 16/29] fix: remove the second action button in the dashboards table (#3012) --- .../src/container/ListOfDashboard/index.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index 9a91d72331..60da406603 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -75,8 +75,8 @@ function ListOfAllDashboard(): JSX.Element { errorMessage: '', }); - const columns: TableColumnProps[] = useMemo( - () => [ + const columns = useMemo(() => { + const tableColumns: TableColumnProps[] = [ { title: 'Name', dataIndex: 'name', @@ -118,19 +118,19 @@ function ListOfAllDashboard(): JSX.Element { }, render: DateComponent, }, - ], - [], - ); + ]; - if (action) { - columns.push({ - title: 'Action', - dataIndex: '', - key: 'x', - width: 40, - render: DeleteButton, - }); - } + if (action) { + tableColumns.push({ + title: 'Action', + dataIndex: '', + width: 40, + render: DeleteButton, + }); + } + + return tableColumns; + }, [action]); const data: Data[] = (filteredDashboards || dashboards).map((e) => ({ createdBy: e.created_at, From 10a3a6d3e58397aed551d055291aabc508a4bbfc Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Tue, 4 Jul 2023 08:24:34 +0300 Subject: [PATCH 17/29] feat: add the traces view for the traces explorer (#2966) * feat: add dynamic table based on query * feat: add the list view for the traces explorer * feat: add the list view for the traces explorer * feat: add the list view for the traces explorer * feat: add the table view for the traces explorer * feat: add the table view for the traces explorer * feat: add the trace view for the traces explorer * feat: update the traces view tab for the traces explorer page * feat: update the traces view --------- Co-authored-by: Yevhen Shevchenko Co-authored-by: Nazarenko19 Co-authored-by: Vishal Sharma --- frontend/src/constants/queryBuilder.ts | 2 +- frontend/src/container/Controls/index.tsx | 9 +- .../QueryBuilder/components/Query/Query.tsx | 17 ++- .../QueryTable/QueryTable.intefaces.ts | 2 + .../src/container/QueryTable/QueryTable.tsx | 7 +- .../src/container/TimeSeriesView/index.tsx | 2 +- .../TracesExplorer/Controls/index.tsx | 46 ++++-- .../TracesExplorer/QuerySection/index.tsx | 5 +- .../TracesExplorer/TracesView/configs.tsx | 49 +++++++ .../TracesExplorer/TracesView/index.tsx | 87 ++++++++++++ .../TracesExplorer/TracesView/styles.ts | 13 ++ .../hooks/queryBuilder/useQueryOperations.ts | 5 + frontend/src/hooks/queryPagination/config.ts | 7 +- frontend/src/hooks/queryPagination/types.ts | 2 +- .../queryPagination/useQueryPagination.ts | 28 +++- frontend/src/hooks/queryPagination/utils.ts | 24 ++-- frontend/src/hooks/useUrlQueryData.ts | 2 +- .../getOperatorsBySourceAndPanelType.ts | 8 +- .../queryBuilderMappers/mapQueryDataToApi.ts | 6 + .../lib/query/createTableColumnsFromQuery.ts | 133 ++++++++++++------ frontend/src/pages/TracesExplorer/index.tsx | 38 ++++- frontend/src/pages/TracesExplorer/utils.tsx | 20 ++- .../actions/dashboard/getQueryResults.ts | 9 +- frontend/src/types/api/widgets/getQuery.ts | 2 +- frontend/src/types/common/operations.types.ts | 1 + frontend/src/types/common/queryBuilder.ts | 4 +- 26 files changed, 424 insertions(+), 104 deletions(-) create mode 100644 frontend/src/container/TracesExplorer/TracesView/configs.tsx create mode 100644 frontend/src/container/TracesExplorer/TracesView/index.tsx create mode 100644 frontend/src/container/TracesExplorer/TracesView/styles.ts diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 52be70442f..ef0185c399 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -232,8 +232,8 @@ export const PANEL_TYPES: Record = { VALUE: 'value', TABLE: 'table', LIST: 'list', - EMPTY_WIDGET: 'EMPTY_WIDGET', TRACE: 'trace', + EMPTY_WIDGET: 'EMPTY_WIDGET', }; export type IQueryBuilderState = 'search'; diff --git a/frontend/src/container/Controls/index.tsx b/frontend/src/container/Controls/index.tsx index e552bc9f28..8971214a05 100644 --- a/frontend/src/container/Controls/index.tsx +++ b/frontend/src/container/Controls/index.tsx @@ -1,13 +1,14 @@ import { LeftOutlined, RightOutlined } from '@ant-design/icons'; import { Button, Select } from 'antd'; -import { Pagination } from 'hooks/queryPagination'; +import { DEFAULT_PER_PAGE_OPTIONS, Pagination } from 'hooks/queryPagination'; import { memo, useMemo } from 'react'; -import { defaultSelectStyle, ITEMS_PER_PAGE_OPTIONS } from './config'; +import { defaultSelectStyle } from './config'; import { Container } from './styles'; function Controls({ offset = 0, + perPageOptions = DEFAULT_PER_PAGE_OPTIONS, isLoading, totalCount, countPerPage, @@ -51,7 +52,7 @@ function Controls({ value={countPerPage} onChange={handleCountItemsPerPageChange} > - {ITEMS_PER_PAGE_OPTIONS.map((count) => ( + {perPageOptions.map((count) => ( -
- - - {renderAdditionalFilters()} - - - + {!isTracePanelType && ( + + + + {renderAdditionalFilters()} + + + + )} ReactNode; + modifyColumns?: (columns: ColumnsType) => ColumnsType; }; diff --git a/frontend/src/container/QueryTable/QueryTable.tsx b/frontend/src/container/QueryTable/QueryTable.tsx index 8265a8494d..d08abbd592 100644 --- a/frontend/src/container/QueryTable/QueryTable.tsx +++ b/frontend/src/container/QueryTable/QueryTable.tsx @@ -13,6 +13,7 @@ export function QueryTable({ queryTableData, query, renderActionCell, + modifyColumns, ...props }: QueryTableProps): JSX.Element { const { columns, dataSource } = useMemo( @@ -39,9 +40,13 @@ export function QueryTable({ return currentColumns; }, [columns]); + const tableColumns = modifyColumns + ? modifyColumns(modifiedColumns) + : modifiedColumns; + return ( {}; - const handleNavigatePrevious = (): void => {}; - const handleNavigateNext = (): void => {}; +function TraceExplorerControls({ + isLoading, + totalCount, + perPageOptions, + config, +}: TraceExplorerControlsProps): JSX.Element | null { + const { + pagination, + handleCountItemsPerPageChange, + handleNavigateNext, + handleNavigatePrevious, + } = useQueryPagination(totalCount, perPageOptions); return ( + {config && } + ); } +TraceExplorerControls.defaultProps = { + config: null, +}; + +type TraceExplorerControlsProps = Pick< + ControlsProps, + 'isLoading' | 'totalCount' | 'perPageOptions' +> & { + config?: OptionsMenuConfig | null; +}; + export default memo(TraceExplorerControls); diff --git a/frontend/src/container/TracesExplorer/QuerySection/index.tsx b/frontend/src/container/TracesExplorer/QuerySection/index.tsx index f4dafe16ab..dbb922c139 100644 --- a/frontend/src/container/TracesExplorer/QuerySection/index.tsx +++ b/frontend/src/container/TracesExplorer/QuerySection/index.tsx @@ -3,6 +3,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; import { QueryBuilder } from 'container/QueryBuilder'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { memo } from 'react'; import { DataSource } from 'types/common/queryBuilder'; import { ButtonWrapper, Container } from './styles'; @@ -10,7 +11,7 @@ import { ButtonWrapper, Container } from './styles'; function QuerySection(): JSX.Element { const { handleRunQuery } = useQueryBuilder(); - const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.TIME_SERIES); + const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); return ( @@ -32,4 +33,4 @@ function QuerySection(): JSX.Element { ); } -export default QuerySection; +export default memo(QuerySection); diff --git a/frontend/src/container/TracesExplorer/TracesView/configs.tsx b/frontend/src/container/TracesExplorer/TracesView/configs.tsx new file mode 100644 index 0000000000..3bbf528eee --- /dev/null +++ b/frontend/src/container/TracesExplorer/TracesView/configs.tsx @@ -0,0 +1,49 @@ +import { Typography } from 'antd'; +import { ColumnsType } from 'antd/es/table'; +import ROUTES from 'constants/routes'; +import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util'; +import { DEFAULT_PER_PAGE_OPTIONS } from 'hooks/queryPagination'; +import { generatePath } from 'react-router-dom'; +import { ListItem } from 'types/api/widgets/getQuery'; + +export const PER_PAGE_OPTIONS: number[] = [10, ...DEFAULT_PER_PAGE_OPTIONS]; + +export const columns: ColumnsType = [ + { + title: 'Root Service Name', + dataIndex: 'subQuery.serviceName', + key: 'serviceName', + }, + { + title: 'Root Operation Name', + dataIndex: 'subQuery.name', + key: 'name', + }, + { + title: 'Root Duration (in ms)', + dataIndex: 'subQuery.durationNano', + key: 'durationNano', + render: (duration: number): JSX.Element => ( + {getMs(String(duration))}ms + ), + }, + { + title: 'No of Spans', + dataIndex: 'span_count', + key: 'span_count', + }, + { + title: 'TraceID', + dataIndex: 'traceID', + key: 'traceID', + render: (traceID: string): JSX.Element => ( + + {traceID} + + ), + }, +]; diff --git a/frontend/src/container/TracesExplorer/TracesView/index.tsx b/frontend/src/container/TracesExplorer/TracesView/index.tsx new file mode 100644 index 0000000000..e4ef1e5ec5 --- /dev/null +++ b/frontend/src/container/TracesExplorer/TracesView/index.tsx @@ -0,0 +1,87 @@ +import Typography from 'antd/es/typography/Typography'; +import { ResizeTable } from 'components/ResizeTable'; +import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { Pagination, URL_PAGINATION } from 'hooks/queryPagination'; +import useUrlQueryData from 'hooks/useUrlQueryData'; +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import TraceExplorerControls from '../Controls'; +import { columns, PER_PAGE_OPTIONS } from './configs'; +import { ActionsContainer, Container } from './styles'; + +function TracesView(): JSX.Element { + const { stagedQuery, panelType } = useQueryBuilder(); + + const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const { queryData: paginationQueryData } = useUrlQueryData( + URL_PAGINATION, + ); + + const { data, isLoading } = useGetQueryRange( + { + query: stagedQuery || initialQueriesMap.traces, + graphType: panelType || PANEL_TYPES.TRACE, + selectedTime: 'GLOBAL_TIME', + globalSelectedInterval: globalSelectedTime, + params: { + dataSource: 'traces', + }, + tableParams: { + pagination: paginationQueryData, + }, + }, + { + queryKey: [ + REACT_QUERY_KEY.GET_QUERY_RANGE, + globalSelectedTime, + maxTime, + minTime, + stagedQuery, + panelType, + paginationQueryData, + ], + enabled: !!stagedQuery && panelType === PANEL_TYPES.TRACE, + }, + ); + + const responseData = data?.payload?.data?.newResult?.data?.result[0]?.list; + const tableData = useMemo( + () => responseData?.map((listItem) => listItem.data), + [responseData], + ); + + return ( + + + + Showing up to X of the slowest traces form the selected time range + + + + + + ); +} + +export default TracesView; diff --git a/frontend/src/container/TracesExplorer/TracesView/styles.ts b/frontend/src/container/TracesExplorer/TracesView/styles.ts new file mode 100644 index 0000000000..f9c9a7c8ea --- /dev/null +++ b/frontend/src/container/TracesExplorer/TracesView/styles.ts @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + gap: 15px; +`; + +export const ActionsContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; diff --git a/frontend/src/hooks/queryBuilder/useQueryOperations.ts b/frontend/src/hooks/queryBuilder/useQueryOperations.ts index 00cbdebcff..540614d838 100644 --- a/frontend/src/hooks/queryBuilder/useQueryOperations.ts +++ b/frontend/src/hooks/queryBuilder/useQueryOperations.ts @@ -122,6 +122,10 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => { [query.dataSource], ); + const isTracePanelType = useMemo(() => panelType === PANEL_TYPES.TRACE, [ + panelType, + ]); + useEffect(() => { if (initialDataSource && dataSource !== initialDataSource) return; @@ -142,6 +146,7 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => { }, [dataSource, aggregateOperator, getNewListOfAdditionalFilters]); return { + isTracePanelType, isMetricsDataSource, operators, listOfAdditionalFilters, diff --git a/frontend/src/hooks/queryPagination/config.ts b/frontend/src/hooks/queryPagination/config.ts index c67493a070..72dc032051 100644 --- a/frontend/src/hooks/queryPagination/config.ts +++ b/frontend/src/hooks/queryPagination/config.ts @@ -1,8 +1,3 @@ -import { Pagination } from './types'; - export const URL_PAGINATION = 'pagination'; -export const defaultPaginationConfig: Pagination = { - offset: 0, - limit: 25, -}; +export const DEFAULT_PER_PAGE_OPTIONS: number[] = [25, 50, 100, 200]; diff --git a/frontend/src/hooks/queryPagination/types.ts b/frontend/src/hooks/queryPagination/types.ts index f8a40b14a2..8bea38eef9 100644 --- a/frontend/src/hooks/queryPagination/types.ts +++ b/frontend/src/hooks/queryPagination/types.ts @@ -1,4 +1,4 @@ export interface Pagination { offset: number; - limit: 25 | 50 | 100 | 200; + limit: number; } diff --git a/frontend/src/hooks/queryPagination/useQueryPagination.ts b/frontend/src/hooks/queryPagination/useQueryPagination.ts index 8864e35d10..29cee3ecb8 100644 --- a/frontend/src/hooks/queryPagination/useQueryPagination.ts +++ b/frontend/src/hooks/queryPagination/useQueryPagination.ts @@ -1,12 +1,23 @@ import { ControlsProps } from 'container/Controls'; import useUrlQueryData from 'hooks/useUrlQueryData'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; -import { defaultPaginationConfig, URL_PAGINATION } from './config'; +import { DEFAULT_PER_PAGE_OPTIONS, URL_PAGINATION } from './config'; import { Pagination } from './types'; -import { checkIsValidPaginationData } from './utils'; +import { + checkIsValidPaginationData, + getDefaultPaginationConfig, +} from './utils'; + +const useQueryPagination = ( + totalCount: number, + perPageOptions: number[] = DEFAULT_PER_PAGE_OPTIONS, +): UseQueryPagination => { + const defaultPaginationConfig = useMemo( + () => getDefaultPaginationConfig(perPageOptions), + [perPageOptions], + ); -const useQueryPagination = (totalCount: number): UseQueryPagination => { const { query: paginationQuery, queryData: paginationQueryData, @@ -45,12 +56,19 @@ const useQueryPagination = (totalCount: number): UseQueryPagination => { useEffect(() => { const isValidPaginationData = checkIsValidPaginationData( paginationQueryData || defaultPaginationConfig, + perPageOptions, ); if (paginationQuery && isValidPaginationData) return; redirectWithCurrentPagination(defaultPaginationConfig); - }, [paginationQuery, paginationQueryData, redirectWithCurrentPagination]); + }, [ + defaultPaginationConfig, + perPageOptions, + paginationQuery, + paginationQueryData, + redirectWithCurrentPagination, + ]); return { pagination: paginationQueryData || defaultPaginationConfig, diff --git a/frontend/src/hooks/queryPagination/utils.ts b/frontend/src/hooks/queryPagination/utils.ts index 694559aa1a..217a19afe1 100644 --- a/frontend/src/hooks/queryPagination/utils.ts +++ b/frontend/src/hooks/queryPagination/utils.ts @@ -1,12 +1,20 @@ +import { DEFAULT_PER_PAGE_OPTIONS } from './config'; import { Pagination } from './types'; -export const checkIsValidPaginationData = ({ - limit, - offset, -}: Pagination): boolean => +export const checkIsValidPaginationData = ( + { limit, offset }: Pagination, + perPageOptions: number[], +): boolean => Boolean( - limit && - (limit === 25 || limit === 50 || limit === 100 || limit === 200) && - offset && - offset > 0, + Number.isInteger(limit) && + limit > 0 && + offset >= 0 && + perPageOptions.find((option) => option === limit), ); + +export const getDefaultPaginationConfig = ( + perPageOptions = DEFAULT_PER_PAGE_OPTIONS, +): Pagination => ({ + offset: 0, + limit: perPageOptions[0], +}); diff --git a/frontend/src/hooks/useUrlQueryData.ts b/frontend/src/hooks/useUrlQueryData.ts index a4304c8620..1a0e556afd 100644 --- a/frontend/src/hooks/useUrlQueryData.ts +++ b/frontend/src/hooks/useUrlQueryData.ts @@ -10,7 +10,7 @@ const useUrlQueryData = ( const location = useLocation(); const urlQuery = useUrlQuery(); - const query = urlQuery.get(queryKey); + const query = useMemo(() => urlQuery.get(queryKey), [queryKey, urlQuery]); const queryData: T = useMemo(() => (query ? JSON.parse(query) : defaultData), [ query, diff --git a/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts b/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts index 21b3bc7498..dd6869847b 100644 --- a/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts +++ b/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts @@ -15,12 +15,16 @@ export const getOperatorsBySourceAndPanelType = ({ }: GetQueryOperatorsParams): SelectOption[] => { let operatorsByDataSource = mapOfOperators[dataSource]; - if (panelType === PANEL_TYPES.LIST) { + if (panelType === PANEL_TYPES.LIST || panelType === PANEL_TYPES.TRACE) { operatorsByDataSource = operatorsByDataSource.filter( (operator) => operator.value === StringOperators.NOOP, ); } - if (dataSource !== DataSource.METRICS && panelType !== PANEL_TYPES.LIST) { + if ( + dataSource !== DataSource.METRICS && + panelType !== PANEL_TYPES.LIST && + panelType !== PANEL_TYPES.TRACE + ) { operatorsByDataSource = operatorsByDataSource.filter( (operator) => operator.value !== StringOperators.NOOP, ); diff --git a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi.ts b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi.ts index c0016fa0de..f89c8b025e 100644 --- a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi.ts +++ b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi.ts @@ -1,3 +1,4 @@ +import { GetQueryResultsProps } from 'store/actions/dashboard/getQueryResults'; import { MapData, MapQueryDataToApiResult, @@ -6,6 +7,7 @@ import { export const mapQueryDataToApi = ( data: Data[], nameField: Key, + tableParams?: GetQueryResultsProps['tableParams'], ): MapQueryDataToApiResult> => { const newLegendMap: Record = {}; @@ -14,6 +16,10 @@ export const mapQueryDataToApi = ( ...acc, [query[nameField] as string]: { ...query, + ...tableParams?.pagination, + ...(tableParams?.selectColumns + ? { selectColumns: tableParams?.selectColumns } + : null), }, }; diff --git a/frontend/src/lib/query/createTableColumnsFromQuery.ts b/frontend/src/lib/query/createTableColumnsFromQuery.ts index bbb03e284d..7aaa10480d 100644 --- a/frontend/src/lib/query/createTableColumnsFromQuery.ts +++ b/frontend/src/lib/query/createTableColumnsFromQuery.ts @@ -5,7 +5,7 @@ import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces'; import { toCapitalize } from 'lib/toCapitalize'; import { ReactNode } from 'react'; import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData'; -import { QueryDataV3, SeriesItem } from 'types/api/widgets/getQuery'; +import { ListItem, QueryDataV3, SeriesItem } from 'types/api/widgets/getQuery'; import { v4 as uuid } from 'uuid'; type CreateTableDataFromQueryParams = Pick< @@ -47,6 +47,10 @@ type GetDynamicColumns = ( query: Query, ) => DynamicColumns; +type ListItemData = ListItem['data']; +type ListItemKey = keyof ListItemData; +type SeriesItemLabels = SeriesItem['labels']; + const isFormula = (queryName: string): boolean => FORMULA_REGEXP.test(queryName); @@ -72,57 +76,77 @@ const prepareColumnTitle = (title: string): string => { return toCapitalize(title); }; +const createLabels = ( + labels: T, + label: keyof T, + dynamicColumns: DynamicColumns, +): void => { + if (isColumnExist(label as string, dynamicColumns)) return; + if (isFormula(label as string)) return; + + const labelValue = labels[label]; + + const isNumber = !Number.isNaN(parseFloat(String(labelValue))); + + const fieldObj: DynamicColumn = { + key: label as string, + data: [], + type: 'field', + sortable: isNumber, + }; + + dynamicColumns.push(fieldObj); +}; + const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => { const dynamicColumns: DynamicColumns = []; queryTableData.forEach((currentQuery) => { - if (!currentQuery.series) return; - - if (!isColumnExist('timestamp', dynamicColumns)) { - dynamicColumns.push({ - key: 'timestamp', - data: [], - type: 'field', - sortable: true, + if (currentQuery.list) { + currentQuery.list.forEach((listItem) => { + Object.keys(listItem.data).forEach((label) => { + createLabels( + listItem.data, + label as ListItemKey, + dynamicColumns, + ); + }); }); } - currentQuery.series.forEach((seria) => { - Object.keys(seria.labels).forEach((label) => { - if (isColumnExist(label, dynamicColumns)) return; - if (isFormula(label)) return; - - const labelValue = seria.labels[label]; - - const isNumber = !Number.isNaN(parseFloat(labelValue)); - - const fieldObj: DynamicColumn = { - key: label, + if (currentQuery.series) { + if (!isColumnExist('timestamp', dynamicColumns)) { + dynamicColumns.push({ + key: 'timestamp', data: [], type: 'field', - sortable: isNumber, - }; + sortable: true, + }); + } - dynamicColumns.push(fieldObj); + currentQuery.series.forEach((seria) => { + Object.keys(seria.labels).forEach((label) => { + createLabels(seria.labels, label, dynamicColumns); + }); }); - }); - if (!isFormula(currentQuery.queryName)) { - const builderQuery = query.builder.queryData.find( - (q) => q.queryName === currentQuery.queryName, - ); + if (!isFormula(currentQuery.queryName)) { + const builderQuery = query.builder.queryData.find( + (q) => q.queryName === currentQuery.queryName, + ); - const operator = builderQuery ? builderQuery.aggregateOperator : ''; + const operator = builderQuery ? builderQuery.aggregateOperator : ''; - if (isColumnExist(operator, dynamicColumns)) return; + if (isColumnExist(operator, dynamicColumns)) return; - const operatorColumn: DynamicColumn = { - key: operator, - data: [], - type: 'operator', - sortable: true, - }; - dynamicColumns.push(operatorColumn); + const operatorColumn: DynamicColumn = { + key: operator, + data: [], + type: 'operator', + sortable: true, + }; + dynamicColumns.push(operatorColumn); + } } }); @@ -194,22 +218,47 @@ const fillDataFromSeria = ( }); }; +const fillDataFromList = ( + listItem: ListItem, + columns: DynamicColumns, +): void => { + columns.forEach((column) => { + if (isFormula(column.key as string)) return; + + Object.keys(listItem.data).forEach((label) => { + if (column.key === label) { + if (listItem.data[label as ListItemKey]) { + column.data.push(listItem.data[label as ListItemKey] as string | number); + } else { + column.data.push('N/A'); + } + } + }); + }); +}; + const fillColumnsData: FillColumnData = (queryTableData, cols, query) => { const fields = cols.filter((item) => item.type === 'field'); const operators = cols.filter((item) => item.type === 'operator'); const resultColumns = [...fields, ...operators]; queryTableData.forEach((currentQuery) => { - if (!currentQuery.series) return; - const currentOperator = getQueryOperator( query.builder.queryData, currentQuery.queryName, ); - currentQuery.series.forEach((seria) => { - fillDataFromSeria(seria, resultColumns, currentOperator); - }); + if (currentQuery.series) { + currentQuery.series.forEach((seria) => { + fillDataFromSeria(seria, resultColumns, currentOperator); + }); + } + + if (currentQuery.list) { + currentQuery.list.forEach((listItem) => { + fillDataFromList(listItem, resultColumns); + }); + } }); const rowsLength = resultColumns.length > 0 ? resultColumns[0].data.length : 0; diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index 22f7a4f153..d6073fc466 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -16,7 +16,7 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; -import { useCallback, useMemo } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { generatePath } from 'react-router-dom'; import { Dashboard } from 'types/api/dashboard/getAll'; import { DataSource } from 'types/common/queryBuilder'; @@ -34,19 +34,38 @@ function TracesExplorer(): JSX.Element { redirectWithQueryBuilderData, } = useQueryBuilder(); - const tabsItems = getTabsItems(); - const currentTab = panelType || PANEL_TYPES.TIME_SERIES; + const currentTab = panelType || PANEL_TYPES.LIST; + + const isMultipleQueries = useMemo( + () => + currentQuery.builder.queryData.length > 1 || + currentQuery.builder.queryFormulas.length > 0, + [currentQuery], + ); const defaultQuery = useMemo( () => updateAllQueriesOperators( initialQueriesMap.traces, - PANEL_TYPES.TIME_SERIES, + PANEL_TYPES.LIST, DataSource.TRACES, ), [updateAllQueriesOperators], ); + const isGroupByExist = useMemo(() => { + const groupByCount: number = currentQuery.builder.queryData.reduce( + (acc, query) => acc + query.groupBy.length, + 0, + ); + + return groupByCount > 0; + }, [currentQuery]); + + const tabsItems = getTabsItems({ + isListViewDisabled: isMultipleQueries || isGroupByExist, + }); + const exportDefaultQuery = useMemo( () => updateAllQueriesOperators( @@ -114,6 +133,17 @@ function TracesExplorer(): JSX.Element { useShareBuilderUrl(defaultQuery); + useEffect(() => { + const shouldChangeView = isMultipleQueries || isGroupByExist; + + if ( + (currentTab === PANEL_TYPES.LIST || currentTab === PANEL_TYPES.TRACE) && + shouldChangeView + ) { + handleTabChange(PANEL_TYPES.TIME_SERIES); + } + }, [currentTab, isMultipleQueries, isGroupByExist, handleTabChange]); + return ( <> diff --git a/frontend/src/pages/TracesExplorer/utils.tsx b/frontend/src/pages/TracesExplorer/utils.tsx index 7c38207281..116a125c83 100644 --- a/frontend/src/pages/TracesExplorer/utils.tsx +++ b/frontend/src/pages/TracesExplorer/utils.tsx @@ -1,17 +1,25 @@ import { TabsProps } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; import TimeSeriesView from 'container/TimeSeriesView'; +import TracesView from 'container/TracesExplorer/TracesView'; import { DataSource } from 'types/common/queryBuilder'; -export const getTabsItems = (): TabsProps['items'] => [ +interface GetTabsItemsProps { + isListViewDisabled: boolean; +} + +export const getTabsItems = ({ + isListViewDisabled, +}: GetTabsItemsProps): TabsProps['items'] => [ + { + label: 'Traces', + key: PANEL_TYPES.TRACE, + children: , + disabled: isListViewDisabled, + }, { label: 'Time Series', key: PANEL_TYPES.TIME_SERIES, children: , }, - { - label: 'Traces', - key: PANEL_TYPES.TRACE, - children:
Traces tab
, - }, ]; diff --git a/frontend/src/store/actions/dashboard/getQueryResults.ts b/frontend/src/store/actions/dashboard/getQueryResults.ts index 137730770b..1487b3f7c8 100644 --- a/frontend/src/store/actions/dashboard/getQueryResults.ts +++ b/frontend/src/store/actions/dashboard/getQueryResults.ts @@ -16,12 +16,14 @@ import { SuccessResponse } from 'types/api'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; +import { Pagination } from 'hooks/queryPagination'; export async function GetMetricQueryRange({ query, globalSelectedInterval, graphType, selectedTime, + tableParams, variables = {}, params = {}, }: GetQueryResultsProps): Promise> { @@ -38,8 +40,9 @@ export async function GetMetricQueryRange({ switch (query.queryType) { case EQueryType.QUERY_BUILDER: { const { queryData: data, queryFormulas } = query.builder; - const currentQueryData = mapQueryDataToApi(data, 'queryName'); + const currentQueryData = mapQueryDataToApi(data, 'queryName', tableParams); const currentFormulas = mapQueryDataToApi(queryFormulas, 'queryName'); + const builderQueries = { ...currentQueryData.data, ...currentFormulas.data, @@ -140,4 +143,8 @@ export interface GetQueryResultsProps { globalSelectedInterval: Time; variables?: Record; params?: Record; + tableParams?: { + pagination?: Pagination; + selectColumns?: any; + }; } diff --git a/frontend/src/types/api/widgets/getQuery.ts b/frontend/src/types/api/widgets/getQuery.ts index f97ffb59e2..0b36af1541 100644 --- a/frontend/src/types/api/widgets/getQuery.ts +++ b/frontend/src/types/api/widgets/getQuery.ts @@ -5,7 +5,7 @@ export interface PayloadProps { result: QueryData[]; } -type ListItem = { timestamp: string; data: Omit }; +export type ListItem = { timestamp: string; data: Omit }; export interface QueryData { metric: { diff --git a/frontend/src/types/common/operations.types.ts b/frontend/src/types/common/operations.types.ts index 409f66cc5c..d5872c8bbe 100644 --- a/frontend/src/types/common/operations.types.ts +++ b/frontend/src/types/common/operations.types.ts @@ -18,6 +18,7 @@ export type HandleChangeQueryData = < export type UseQueryOperations = ( params: UseQueryOperationsParams, ) => { + isTracePanelType: boolean; isMetricsDataSource: boolean; operators: SelectOption[]; listOfAdditionalFilters: string[]; diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts index 774ec5a65c..4b6801eccf 100644 --- a/frontend/src/types/common/queryBuilder.ts +++ b/frontend/src/types/common/queryBuilder.ts @@ -143,8 +143,8 @@ export type PanelTypeKeys = | 'VALUE' | 'TABLE' | 'LIST' - | 'EMPTY_WIDGET' - | 'TRACE'; + | 'TRACE' + | 'EMPTY_WIDGET'; export type ReduceOperators = 'last' | 'sum' | 'avg' | 'max' | 'min'; From 193b04ff0f23dc9e0417897dc35b21c26364ae8f Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Tue, 4 Jul 2023 19:05:20 +0530 Subject: [PATCH 18/29] feat: minor fixes to logs QB (#3022) * feat: minor fixes to logs QB * fix: panel type check added * fix: panel type check added * fix: order by logic updated --- pkg/query-service/app/logs/v3/enrich_query.go | 2 +- .../app/logs/v3/query_builder.go | 16 +++++--------- .../app/logs/v3/query_builder_test.go | 21 ++++++++++++++++++- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/pkg/query-service/app/logs/v3/enrich_query.go b/pkg/query-service/app/logs/v3/enrich_query.go index 88f9097814..5cf15618e2 100644 --- a/pkg/query-service/app/logs/v3/enrich_query.go +++ b/pkg/query-service/app/logs/v3/enrich_query.go @@ -131,7 +131,7 @@ func enrichFieldWithMetadata(field v3.AttributeKey, fields map[string]v3.Attribu // check if the field is present in the fields map if existingField, ok := fields[field.Key]; ok { if existingField.IsColumn { - return field + return existingField } field.Type = existingField.Type field.DataType = existingField.DataType diff --git a/pkg/query-service/app/logs/v3/query_builder.go b/pkg/query-service/app/logs/v3/query_builder.go index 6ff58a2497..1e49250865 100644 --- a/pkg/query-service/app/logs/v3/query_builder.go +++ b/pkg/query-service/app/logs/v3/query_builder.go @@ -321,19 +321,13 @@ func orderByAttributeKeyTags(panelType v3.PanelType, aggregatorOperator v3.Aggre } orderByArray := orderBy(panelType, items, groupTags) - found := false - for i := 0; i < len(orderByArray); i++ { - if strings.Compare(orderByArray[i], constants.TIMESTAMP) == 0 { - orderByArray[i] = "ts" - break - } - } - if !found { - if aggregatorOperator == v3.AggregateOperatorNoOp { + if panelType == v3.PanelTypeList { + if len(orderByArray) == 0 { orderByArray = append(orderByArray, constants.TIMESTAMP) - } else { - orderByArray = append(orderByArray, "ts") } + } else { + // since in other aggregation operator we will have to add ts as it will not be present in group by + orderByArray = append(orderByArray, "ts") } str := strings.Join(orderByArray, ",") diff --git a/pkg/query-service/app/logs/v3/query_builder_test.go b/pkg/query-service/app/logs/v3/query_builder_test.go index 5103c7a177..2712b8a874 100644 --- a/pkg/query-service/app/logs/v3/query_builder_test.go +++ b/pkg/query-service/app/logs/v3/query_builder_test.go @@ -589,7 +589,7 @@ var testBuildLogsQueryData = []struct { }, { Name: "Test Noop", - PanelType: v3.PanelTypeGraph, + PanelType: v3.PanelTypeList, Start: 1680066360726210000, End: 1680066458000000000, Step: 60, @@ -605,6 +605,25 @@ var testBuildLogsQueryData = []struct { "CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string " + "from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) order by timestamp", }, + { + Name: "Test Noop order by custom", + PanelType: v3.PanelTypeList, + Start: 1680066360726210000, + End: 1680066458000000000, + Step: 60, + BuilderQuery: &v3.BuilderQuery{ + SelectColumns: []v3.AttributeKey{}, + QueryName: "A", + AggregateOperator: v3.AggregateOperatorNoOp, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}, + OrderBy: []v3.OrderBy{{ColumnName: "method", Order: "ASC", IsColumn: true}}, + }, + ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," + + "CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64," + + "CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string " + + "from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) order by method ASC", + }, { Name: "Test aggregate with having clause", PanelType: v3.PanelTypeGraph, From b7c50cc76d68d5f254545cf5042d719c3871cfab Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Tue, 4 Jul 2023 19:59:36 +0530 Subject: [PATCH 19/29] feat: add time support in formula (#2961) --- pkg/query-service/app/queryBuilder/query_builder.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/query-service/app/queryBuilder/query_builder.go b/pkg/query-service/app/queryBuilder/query_builder.go index 038b06f312..92325acd45 100644 --- a/pkg/query-service/app/queryBuilder/query_builder.go +++ b/pkg/query-service/app/queryBuilder/query_builder.go @@ -32,6 +32,8 @@ var SupportedFunctions = []string{ "atan", "degrees", "radians", + "now", + "toUnixTimestamp", } var EvalFuncs = map[string]govaluate.ExpressionFunction{} From bc400c2bcf55d1b167729af10f4158961c08078d Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Wed, 5 Jul 2023 06:57:39 +0530 Subject: [PATCH 20/29] feat: trace view and list view for traces (#2847) * feat: checkpoint * feat: add select columns support to list view * chore: add more error handling * feat: always return timestamp, spanID, traceID Always return timestamp, spanID, traceID in list view * test: update and add new tests * chore: remove deprecated const * chore: addressed review comments * fix: add support for timestamp ordering and fix logic related to timestamp orderBy * chore: remove unused variable * fix: edge case and more tests --- .../app/clickhouseReader/reader.go | 5 +- pkg/query-service/app/http_handler.go | 7 +- .../app/traces/v3/query_builder.go | 140 +++++++--- .../app/traces/v3/query_builder_test.go | 247 +++++++++++++++--- pkg/query-service/constants/constants.go | 5 + pkg/query-service/model/v3/v3.go | 3 +- 6 files changed, 334 insertions(+), 73 deletions(-) diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index cf7ed5503c..7185581ab7 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -4223,8 +4223,11 @@ func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([ for idx, v := range vars { if columnNames[idx] == "timestamp" { t = time.Unix(0, int64(*v.(*uint64))) + } else if columnNames[idx] == "timestamp_datetime" { + t = *v.(*time.Time) + } else { + row[columnNames[idx]] = v } - row[columnNames[idx]] = v } rowList = append(rowList, &v3.Row{Timestamp: t, Data: row}) } diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 3025000db3..604b1f046a 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -2684,6 +2684,11 @@ func (aH *APIHandler) getSpanKeysV3(ctx context.Context, queryRangeParams *v3.Qu if err != nil { return nil, err } + // Add timestamp as a span key to allow ordering by timestamp + spanKeys["timestamp"] = v3.AttributeKey{ + Key: "timestamp", + IsColumn: true, + } return spanKeys, nil } } @@ -2725,7 +2730,7 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que return } - if queryRangeParams.CompositeQuery.PanelType == v3.PanelTypeList { + if queryRangeParams.CompositeQuery.PanelType == v3.PanelTypeList || queryRangeParams.CompositeQuery.PanelType == v3.PanelTypeTrace { result, err, errQuriesByName = aH.execClickHouseListQueries(r.Context(), queries) } else { result, err, errQuriesByName = aH.execClickHouseGraphQueries(r.Context(), queries) diff --git a/pkg/query-service/app/traces/v3/query_builder.go b/pkg/query-service/app/traces/v3/query_builder.go index d71f802c8c..abf38b6b40 100644 --- a/pkg/query-service/app/traces/v3/query_builder.go +++ b/pkg/query-service/app/traces/v3/query_builder.go @@ -95,7 +95,7 @@ func enrichKeyWithMetadata(key v3.AttributeKey, keys map[string]v3.AttributeKey) } // getSelectLabels returns the select labels for the query based on groupBy and aggregateOperator -func getSelectLabels(aggregatorOperator v3.AggregateOperator, groupBy []v3.AttributeKey, keys map[string]v3.AttributeKey) (string, error) { +func getSelectLabels(aggregatorOperator v3.AggregateOperator, groupBy []v3.AttributeKey, keys map[string]v3.AttributeKey) string { var selectLabels string if aggregatorOperator == v3.AggregateOperatorNoOp { selectLabels = "" @@ -105,7 +105,16 @@ func getSelectLabels(aggregatorOperator v3.AggregateOperator, groupBy []v3.Attri selectLabels += fmt.Sprintf(", %s as `%s`", filterName, tag.Key) } } - return selectLabels, nil + return selectLabels +} + +func getSelectColumns(sc []v3.AttributeKey, keys map[string]v3.AttributeKey) string { + var columns []string + for _, tag := range sc { + columnName := getColumnName(tag, keys) + columns = append(columns, fmt.Sprintf("%s as `%s` ", columnName, tag.Key)) + } + return strings.Join(columns, ",") } // getZerosForEpochNano returns the number of zeros to be appended to the epoch time for converting it to nanoseconds @@ -208,7 +217,7 @@ func handleEmptyValuesInGroupBy(keys map[string]v3.AttributeKey, groupBy []v3.At return "", nil } -func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName string, keys map[string]v3.AttributeKey) (string, error) { +func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName string, keys map[string]v3.AttributeKey, panelType v3.PanelType) (string, error) { filterSubQuery, err := buildTracesFilterQuery(mq.Filters, keys) if err != nil { @@ -217,10 +226,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str // timerange will be sent in epoch millisecond spanIndexTableTimeFilter := fmt.Sprintf("(timestamp >= '%d' AND timestamp <= '%d')", start*getZerosForEpochNano(start), end*getZerosForEpochNano(end)) - selectLabels, err := getSelectLabels(mq.AggregateOperator, mq.GroupBy, keys) - if err != nil { - return "", err - } + selectLabels := getSelectLabels(mq.AggregateOperator, mq.GroupBy, keys) having := having(mq.Having) if having != "" { @@ -234,7 +240,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_TABLENAME + " where " + spanIndexTableTimeFilter + "%s " + "group by %s%s " + - "order by %sts" + "order by %s" emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(keys, mq.GroupBy) if err != nil { @@ -243,7 +249,8 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str filterSubQuery += emptyValuesInGroupByFilter groupBy := groupByAttributeKeyTags(keys, mq.GroupBy...) - orderBy := orderByAttributeKeyTags(mq.OrderBy, mq.GroupBy) + enrichedOrderBy := enrichOrderBy(mq.OrderBy, keys) + orderBy := orderByAttributeKeyTags(panelType, enrichedOrderBy, mq.GroupBy, keys) aggregationKey := "" if mq.AggregateAttribute.Key != "" { @@ -297,15 +304,48 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str query := fmt.Sprintf(queryTmpl, step, op, filterSubQuery, groupBy, having, orderBy) return query, nil case v3.AggregateOperatorNoOp: - // queryTmpl := constants.TracesSQLSelect + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_TABLENAME + " where %s %s" - // query := fmt.Sprintf(queryTmpl, spanIndexTableTimeFilter, filterSubQuery) - // return query, nil - return "", fmt.Errorf("not implemented, part of traces page") + var query string + if panelType == v3.PanelTypeTrace { + withSubQuery := fmt.Sprintf(constants.TracesExplorerViewSQLSelectWithSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME, spanIndexTableTimeFilter, filterSubQuery) + withSubQuery = addLimitToQuery(withSubQuery, mq.Limit, panelType) + if mq.Offset != 0 { + withSubQuery = addOffsetToQuery(withSubQuery, mq.Offset) + } + query = withSubQuery + ") " + fmt.Sprintf(constants.TracesExplorerViewSQLSelectQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME) + } else if panelType == v3.PanelTypeList { + if len(mq.SelectColumns) == 0 { + return "", fmt.Errorf("select columns cannot be empty for panelType %s", panelType) + } + selectColumns := getSelectColumns(mq.SelectColumns, keys) + queryNoOpTmpl := fmt.Sprintf("SELECT timestamp as timestamp_datetime, spanID, traceID, "+"%s ", selectColumns) + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_TABLENAME + " where %s %s" + " order by %s" + query = fmt.Sprintf(queryNoOpTmpl, spanIndexTableTimeFilter, filterSubQuery, orderBy) + } else { + return "", fmt.Errorf("unsupported aggregate operator %s for panelType %s", mq.AggregateOperator, panelType) + } + return query, nil default: - return "", fmt.Errorf("unsupported aggregate operator") + return "", fmt.Errorf("unsupported aggregate operator %s", mq.AggregateOperator) } } +func enrichOrderBy(items []v3.OrderBy, keys map[string]v3.AttributeKey) []v3.OrderBy { + enrichedItems := []v3.OrderBy{} + for i := 0; i < len(items); i++ { + attributeKey := enrichKeyWithMetadata(v3.AttributeKey{ + Key: items[i].ColumnName, + }, keys) + enrichedItems = append(enrichedItems, v3.OrderBy{ + ColumnName: items[i].ColumnName, + Order: items[i].Order, + Key: attributeKey.Key, + DataType: attributeKey.DataType, + Type: attributeKey.Type, + IsColumn: attributeKey.IsColumn, + }) + } + return enrichedItems +} + // groupBy returns a string of comma separated tags for group by clause // `ts` is always added to the group by clause func groupBy(tags ...string) string { @@ -322,41 +362,66 @@ func groupByAttributeKeyTags(keys map[string]v3.AttributeKey, tags ...v3.Attribu } // orderBy returns a string of comma separated tags for order by clause +// if there are remaining items which are not present in tags they are also added // if the order is not specified, it defaults to ASC -func orderBy(items []v3.OrderBy, tags []string) string { +func orderBy(panelType v3.PanelType, items []v3.OrderBy, tags []string, keys map[string]v3.AttributeKey) []string { var orderBy []string + + // create a lookup + addedToOrderBy := map[string]bool{} + itemsLookup := map[string]v3.OrderBy{} + + for i := 0; i < len(items); i++ { + addedToOrderBy[items[i].ColumnName] = false + itemsLookup[items[i].ColumnName] = items[i] + } + for _, tag := range tags { - found := false - for _, item := range items { - if item.ColumnName == tag { - found = true - orderBy = append(orderBy, fmt.Sprintf("%s %s", item.ColumnName, item.Order)) - break - } - } - if !found { + if item, ok := itemsLookup[tag]; ok { + orderBy = append(orderBy, fmt.Sprintf("%s %s", item.ColumnName, item.Order)) + addedToOrderBy[item.ColumnName] = true + } else { orderBy = append(orderBy, fmt.Sprintf("%s ASC", tag)) } } - // users might want to order by value of aggreagation + // users might want to order by value of aggregation for _, item := range items { if item.ColumnName == constants.SigNozOrderByValue { orderBy = append(orderBy, fmt.Sprintf("value %s", item.Order)) + addedToOrderBy[item.ColumnName] = true } } - return strings.Join(orderBy, ",") + + // add the remaining items + if panelType == v3.PanelTypeList { + for _, item := range items { + // since these are not present in tags we will have to select them correctly + // for list view there is no need to check if it was added since they wont be added yet but this is just for safety + if !addedToOrderBy[item.ColumnName] { + attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn} + name := getColumnName(attr, keys) + orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order)) + } + } + } + return orderBy } -func orderByAttributeKeyTags(items []v3.OrderBy, tags []v3.AttributeKey) string { +func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []v3.AttributeKey, keys map[string]v3.AttributeKey) string { var groupTags []string for _, tag := range tags { groupTags = append(groupTags, tag.Key) } - str := orderBy(items, groupTags) - if len(str) > 0 { - str = str + "," + orderByArray := orderBy(panelType, items, groupTags, keys) + + if panelType == v3.PanelTypeList && len(orderByArray) == 0 { + orderByArray = append(orderByArray, constants.TIMESTAMP+" DESC") + } else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeTable { + orderByArray = append(orderByArray, "ts") } + + str := strings.Join(orderByArray, ",") return str } @@ -393,10 +458,7 @@ func addLimitToQuery(query string, limit uint64, panelType v3.PanelType) string if limit == 0 { limit = 100 } - if panelType == v3.PanelTypeList { - return fmt.Sprintf("%s LIMIT %d", query, limit) - } - return query + return fmt.Sprintf("%s LIMIT %d", query, limit) } func addOffsetToQuery(query string, offset uint64) string { @@ -404,17 +466,19 @@ func addOffsetToQuery(query string, offset uint64) string { } func PrepareTracesQuery(start, end int64, queryType v3.QueryType, panelType v3.PanelType, mq *v3.BuilderQuery, keys map[string]v3.AttributeKey) (string, error) { - query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys) + query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys, panelType) if err != nil { return "", err } if panelType == v3.PanelTypeValue { query, err = reduceToQuery(query, mq.ReduceTo, mq.AggregateOperator) } - query = addLimitToQuery(query, mq.Limit, panelType) + if panelType == v3.PanelTypeList { + query = addLimitToQuery(query, mq.Limit, panelType) - if mq.Offset != 0 { - query = addOffsetToQuery(query, mq.Offset) + if mq.Offset != 0 { + query = addOffsetToQuery(query, mq.Offset) + } } return query, err } diff --git a/pkg/query-service/app/traces/v3/query_builder_test.go b/pkg/query-service/app/traces/v3/query_builder_test.go index 9cda548b9e..97c739d8c1 100644 --- a/pkg/query-service/app/traces/v3/query_builder_test.go +++ b/pkg/query-service/app/traces/v3/query_builder_test.go @@ -244,13 +244,51 @@ var testGetSelectLabelsData = []struct { func TestGetSelectLabels(t *testing.T) { for _, tt := range testGetSelectLabelsData { Convey("testGetSelectLabelsData", t, func() { - selectLabels, err := getSelectLabels(tt.AggregateOperator, tt.GroupByTags, map[string]v3.AttributeKey{}) - So(err, ShouldBeNil) + selectLabels := getSelectLabels(tt.AggregateOperator, tt.GroupByTags, map[string]v3.AttributeKey{}) So(selectLabels, ShouldEqual, tt.SelectLabels) }) } } +var testGetSelectColumnsData = []struct { + Name string + sc []v3.AttributeKey + SelectColumns string +}{ + { + Name: "select columns attribute", + sc: []v3.AttributeKey{{Key: "user.name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + SelectColumns: "stringTagMap['user.name'] as `user.name` ", + }, + { + Name: "select columns resource", + sc: []v3.AttributeKey{{Key: "user.name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}}, + SelectColumns: "resourceTagsMap['user.name'] as `user.name` ", + }, + { + Name: "select columns attribute and resource", + sc: []v3.AttributeKey{ + {Key: "user.name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, + {Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + }, + SelectColumns: "resourceTagsMap['user.name'] as `user.name` ,stringTagMap['host'] as `host` ", + }, + { + Name: "select columns fixed column", + sc: []v3.AttributeKey{{Key: "host", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + SelectColumns: "host as `host` ", + }, +} + +func TestGetSelectColumns(t *testing.T) { + for _, tt := range testGetSelectColumnsData { + Convey("testGetSelectColumnsData", t, func() { + selectColumns := getSelectColumns(tt.sc, map[string]v3.AttributeKey{}) + So(selectColumns, ShouldEqual, tt.SelectColumns) + }) + } +} + var testGetZerosForEpochNanoData = []struct { Name string Epoch int64 @@ -282,13 +320,15 @@ func TestGetZerosForEpochNano(t *testing.T) { } var testOrderBy = []struct { - Name string - Items []v3.OrderBy - Tags []string - Result string + Name string + PanelType v3.PanelType + Items []v3.OrderBy + Tags []string + Result []string }{ { - Name: "Test 1", + Name: "Test 1", + PanelType: v3.PanelTypeGraph, Items: []v3.OrderBy{ { ColumnName: "name", @@ -300,10 +340,11 @@ var testOrderBy = []struct { }, }, Tags: []string{"name"}, - Result: "name asc,value desc", + Result: []string{"name asc", "value desc"}, }, { - Name: "Test 2", + Name: "Test 2", + PanelType: v3.PanelTypeList, Items: []v3.OrderBy{ { ColumnName: "name", @@ -315,10 +356,11 @@ var testOrderBy = []struct { }, }, Tags: []string{"name", "bytes"}, - Result: "name asc,bytes asc", + Result: []string{"name asc", "bytes asc"}, }, { - Name: "Test 3", + Name: "Test 3", + PanelType: v3.PanelTypeList, Items: []v3.OrderBy{ { ColumnName: "name", @@ -334,15 +376,62 @@ var testOrderBy = []struct { }, }, Tags: []string{"name", "bytes"}, - Result: "name asc,bytes asc,value asc", + Result: []string{"name asc", "bytes asc", "value asc"}, + }, + { + Name: "Test 4", + PanelType: v3.PanelTypeList, + Items: []v3.OrderBy{ + { + ColumnName: "name", + Order: "asc", + }, + { + ColumnName: "bytes", + Order: "asc", + }, + { + ColumnName: "response_time", + Order: "desc", + Key: "response_time", + Type: v3.AttributeKeyTypeTag, + DataType: v3.AttributeKeyDataTypeString, + }, + }, + Tags: []string{"name", "bytes"}, + Result: []string{"name asc", "bytes asc", "stringTagMap['response_time'] desc"}, + }, + { + Name: "Test 4", + PanelType: v3.PanelTypeList, + Items: []v3.OrderBy{ + { + ColumnName: "name", + Order: "asc", + }, + { + ColumnName: "bytes", + Order: "asc", + }, + { + ColumnName: "response_time", + Order: "desc", + }, + }, + Tags: []string{}, + Result: []string{"name asc", "bytes asc", "stringTagMap['response_time'] desc"}, }, } func TestOrderBy(t *testing.T) { for _, tt := range testOrderBy { Convey("testOrderBy", t, func() { - res := orderBy(tt.Items, tt.Tags) - So(res, ShouldEqual, tt.Result) + res := orderBy(tt.PanelType, tt.Items, tt.Tags, map[string]v3.AttributeKey{ + "name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + "bytes": {Key: "bytes", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + "response_time": {Key: "response_time", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: false}, + }) + So(res, ShouldResemble, tt.Result) }) } } @@ -357,6 +446,7 @@ var testBuildTracesQueryData = []struct { TableName string AggregateOperator v3.AggregateOperator ExpectedQuery string + PanelType v3.PanelType }{ { Name: "Test aggregate count on fixed column of float64 type", @@ -373,6 +463,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " group by ts order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate rate without aggregate attribute", @@ -388,6 +479,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, count()/60 as value from" + " signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <=" + " '1680066458000000000') group by ts order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate count on fixed column of float64 type with filter", @@ -406,6 +498,7 @@ var testBuildTracesQueryData = []struct { " toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v2" + " where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " AND stringTagMap['customer_id'] = '10001' group by ts order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate count on fixed column of bool type", @@ -422,6 +515,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " group by ts order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate count on a attribute", @@ -438,6 +532,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " AND has(stringTagMap, 'user_name') group by ts order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate count on a fixed column of string type", @@ -454,6 +549,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " AND name != '' group by ts order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate count with filter", @@ -473,6 +569,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " AND numberTagMap['bytes'] > 100.000000 AND has(stringTagMap, 'user_name') group by ts order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate count distinct and order by value", @@ -490,6 +587,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(name))) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " group by ts order by value ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate count distinct on string key", @@ -506,6 +604,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name'])))" + " as value from signoz_traces.distributed_signoz_index_v2 where" + " (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') group by ts order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate count distinct with filter and groupBy", @@ -533,6 +632,7 @@ var testBuildTracesQueryData = []struct { "AND stringTagMap['http.method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " + "AND has(stringTagMap, 'http.method') group by http.method,ts " + "order by http.method ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate count with multiple filter,groupBy and orderBy", @@ -564,6 +664,7 @@ var testBuildTracesQueryData = []struct { "AND stringTagMap['method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " + "AND has(stringTagMap, 'method') AND has(resourceTagsMap, 'x') group by method,x,ts " + "order by method ASC,x ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate avg", @@ -591,6 +692,7 @@ var testBuildTracesQueryData = []struct { "AND stringTagMap['method'] = 'GET' " + "AND has(stringTagMap, 'method') group by method,ts " + "order by method ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate sum", @@ -618,6 +720,7 @@ var testBuildTracesQueryData = []struct { "AND stringTagMap['method'] = 'GET' " + "AND has(stringTagMap, 'method') group by method,ts " + "order by method ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate min", @@ -645,6 +748,7 @@ var testBuildTracesQueryData = []struct { "AND stringTagMap['method'] = 'GET' " + "AND has(stringTagMap, 'method') group by method,ts " + "order by method ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate max", @@ -672,6 +776,7 @@ var testBuildTracesQueryData = []struct { "AND stringTagMap['method'] = 'GET' " + "AND has(stringTagMap, 'method') group by method,ts " + "order by method ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate PXX", @@ -695,6 +800,7 @@ var testBuildTracesQueryData = []struct { "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND has(stringTagMap, 'method') group by method,ts " + "order by method ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate RateSum", @@ -715,6 +821,7 @@ var testBuildTracesQueryData = []struct { ", sum(bytes)/60 as value from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " AND has(stringTagMap, 'method') group by method,ts order by method ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate rate", @@ -736,6 +843,7 @@ var testBuildTracesQueryData = []struct { "from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND has(stringTagMap, 'method') group by method,ts " + "order by method ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate RateSum without fixed column", @@ -758,6 +866,7 @@ var testBuildTracesQueryData = []struct { "from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND has(stringTagMap, 'method') group by method,ts " + "order by method ASC,ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test aggregate with having clause", @@ -781,6 +890,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name']))) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " group by ts having value > 10 order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test count aggregate with having clause and filters", @@ -808,6 +918,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value from " + "signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' AND has(stringTagMap, 'name') group by ts having value > 10 order by ts", + PanelType: v3.PanelTypeGraph, }, { Name: "Test count distinct aggregate with having clause and filters", @@ -835,32 +946,104 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name']))) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' group by ts having value > 10 order by ts", + PanelType: v3.PanelTypeGraph, + }, + { + Name: "Test Noop list view", + Start: 1680066360726210000, + End: 1680066458000000000, + Step: 60, + BuilderQuery: &v3.BuilderQuery{ + SelectColumns: []v3.AttributeKey{ + {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + }, + QueryName: "A", + AggregateOperator: v3.AggregateOperatorNoOp, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}, + }, + ExpectedQuery: "SELECT timestamp as timestamp_datetime, spanID, traceID," + + " name as `name` from signoz_traces.distributed_signoz_index_v2 where " + + "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') order by timestamp DESC", + PanelType: v3.PanelTypeList, + }, + { + Name: "Test Noop list view with order by", + Start: 1680066360726210000, + End: 1680066458000000000, + Step: 60, + BuilderQuery: &v3.BuilderQuery{ + SelectColumns: []v3.AttributeKey{ + {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + }, + QueryName: "A", + AggregateOperator: v3.AggregateOperatorNoOp, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}, + OrderBy: []v3.OrderBy{{ColumnName: "name", Order: "ASC"}}, + }, + ExpectedQuery: "SELECT timestamp as timestamp_datetime, spanID, traceID," + + " name as `name` from signoz_traces.distributed_signoz_index_v2 where " + + "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') order by name ASC", + PanelType: v3.PanelTypeList, + }, + { + Name: "Test Noop list view with order by and filter", + Start: 1680066360726210000, + End: 1680066458000000000, + Step: 60, + BuilderQuery: &v3.BuilderQuery{ + SelectColumns: []v3.AttributeKey{ + {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + }, + QueryName: "A", + AggregateOperator: v3.AggregateOperatorNoOp, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="}, + }}, + OrderBy: []v3.OrderBy{{ColumnName: "name", Order: "ASC"}}, + }, + ExpectedQuery: "SELECT timestamp as timestamp_datetime, spanID, traceID," + + " name as `name` from signoz_traces.distributed_signoz_index_v2 where " + + "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + + " AND stringTagMap['method'] = 'GET' order by name ASC", + PanelType: v3.PanelTypeList, + }, + { + Name: "Test Noop trace view", + Start: 1680066360726210000, + End: 1680066458000000000, + Step: 60, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateOperator: v3.AggregateOperatorNoOp, + Expression: "A", + Filters: &v3.FilterSet{ + Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="}, + }, + }, + }, + ExpectedQuery: "WITH subQuery AS (SELECT distinct on (traceID) traceID, durationNano, serviceName," + + " name FROM signoz_traces.distributed_signoz_index_v2 WHERE parentSpanID = '' AND (timestamp >= '1680066360726210000' AND " + + "timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' ORDER BY durationNano DESC LIMIT 100)" + + " SELECT subQuery.serviceName, subQuery.name, count() AS span_count, subQuery.durationNano, traceID" + + " FROM signoz_traces.distributed_signoz_index_v2 INNER JOIN subQuery ON distributed_signoz_index_v2.traceID" + + " = subQuery.traceID GROUP BY traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName " + + "ORDER BY subQuery.durationNano desc;", + PanelType: v3.PanelTypeTrace, }, - // { - // Name: "Test Noop", - // Start: 1680066360726210000, - // End: 1680066458000000000, - // Step: 60, - // BuilderQuery: &v3.BuilderQuery{ - // SelectColumns: []v3.AttributeKey{}, - // QueryName: "A", - // AggregateOperator: v3.AggregateOperatorNoOp, - // Expression: "A", - // Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}, - // // GroupBy: []v3.AttributeKey{{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, - // // OrderBy: []v3.OrderBy{{ColumnName: "method", Order: "ASC"}}, - // }, - // ExpectedQuery: "", - // }, } func TestBuildTracesQuery(t *testing.T) { for _, tt := range testBuildTracesQueryData { Convey("TestBuildTracesQuery", t, func() { - query, err := buildTracesQuery(tt.Start, tt.End, tt.Step, tt.BuilderQuery, tt.TableName, map[string]v3.AttributeKey{}) + query, err := buildTracesQuery(tt.Start, tt.End, tt.Step, tt.BuilderQuery, tt.TableName, map[string]v3.AttributeKey{ + "name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + }, tt.PanelType) So(err, ShouldBeNil) So(query, ShouldEqual, tt.ExpectedQuery) - }) } } diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go index d86d6df205..31d02b19f6 100644 --- a/pkg/query-service/constants/constants.go +++ b/pkg/query-service/constants/constants.go @@ -236,6 +236,11 @@ const ( "CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64," + "CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64," + "CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string " + TracesExplorerViewSQLSelectWithSubQuery = "WITH subQuery AS (SELECT distinct on (traceID) traceID, durationNano, " + + "serviceName, name FROM %s.%s WHERE parentSpanID = '' AND %s %s ORDER BY durationNano DESC " + TracesExplorerViewSQLSelectQuery = "SELECT subQuery.serviceName, subQuery.name, count() AS " + + "span_count, subQuery.durationNano, traceID FROM %s.%s INNER JOIN subQuery ON %s.traceID = subQuery.traceID GROUP " + + "BY traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName ORDER BY subQuery.durationNano desc;" ) // ReservedColumnTargetAliases identifies result value from a user diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index 7d3028b3d8..57f290f133 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -178,11 +178,12 @@ const ( PanelTypeGraph PanelType = "graph" PanelTypeTable PanelType = "table" PanelTypeList PanelType = "list" + PanelTypeTrace PanelType = "trace" ) func (p PanelType) Validate() error { switch p { - case PanelTypeValue, PanelTypeGraph, PanelTypeTable, PanelTypeList: + case PanelTypeValue, PanelTypeGraph, PanelTypeTable, PanelTypeList, PanelTypeTrace: return nil default: return fmt.Errorf("invalid panel type: %s", p) From 22bbfaf4957887f780cdf7e1a3d480b2457fe7e5 Mon Sep 17 00:00:00 2001 From: Sachin M K Date: Wed, 5 Jul 2023 09:34:20 +0530 Subject: [PATCH 21/29] fix: dashboard query stuck on disabled (#2991) Co-authored-by: Vishal Sharma --- frontend/src/container/QueryBuilder/QueryBuilder.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/QueryBuilder/QueryBuilder.tsx index 8739d6281b..464c4599c1 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.tsx +++ b/frontend/src/container/QueryBuilder/QueryBuilder.tsx @@ -58,7 +58,7 @@ export const QueryBuilder = memo(function QueryBuilder({ const isAvailableToDisableQuery = useMemo( () => - currentQuery.builder.queryData.length > 1 || + currentQuery.builder.queryData.length > 0 || currentQuery.builder.queryFormulas.length > 0, [currentQuery], ); From ea89433dc0ec12063bcd02aa2d65b98ec6e65233 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Wed, 5 Jul 2023 07:51:31 +0300 Subject: [PATCH 22/29] fix: formula for table (#3004) * fix: formula for table * fix: empty column * fix: formula values in table view --------- Co-authored-by: Palash Gupta Co-authored-by: Vishal Sharma Co-authored-by: Srikanth Chekuri --- .../lib/query/createTableColumnsFromQuery.ts | 66 ++++++++----------- frontend/src/lib/toCapitalize.ts | 7 +- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/frontend/src/lib/query/createTableColumnsFromQuery.ts b/frontend/src/lib/query/createTableColumnsFromQuery.ts index 7aaa10480d..168261238f 100644 --- a/frontend/src/lib/query/createTableColumnsFromQuery.ts +++ b/frontend/src/lib/query/createTableColumnsFromQuery.ts @@ -39,7 +39,6 @@ type CreateTableDataFromQuery = ( type FillColumnData = ( queryTableData: QueryDataV3[], dynamicColumns: DynamicColumns, - query: Query, ) => { filledDynamicColumns: DynamicColumns; rowsLength: number }; type GetDynamicColumns = ( @@ -76,13 +75,21 @@ const prepareColumnTitle = (title: string): string => { return toCapitalize(title); }; +const getQueryOperator = ( + queryData: IBuilderQuery[], + currentQueryName: string, +): string => { + const builderQuery = queryData.find((q) => q.queryName === currentQueryName); + + return builderQuery ? builderQuery.aggregateOperator : ''; +}; + const createLabels = ( labels: T, label: keyof T, dynamicColumns: DynamicColumns, ): void => { if (isColumnExist(label as string, dynamicColumns)) return; - if (isFormula(label as string)) return; const labelValue = labels[label]; @@ -130,38 +137,26 @@ const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => { }); }); - if (!isFormula(currentQuery.queryName)) { - const builderQuery = query.builder.queryData.find( - (q) => q.queryName === currentQuery.queryName, - ); + const operator = getQueryOperator( + query.builder.queryData, + currentQuery.queryName, + ); - const operator = builderQuery ? builderQuery.aggregateOperator : ''; + if (operator === '' || isColumnExist(operator, dynamicColumns)) return; - if (isColumnExist(operator, dynamicColumns)) return; - - const operatorColumn: DynamicColumn = { - key: operator, - data: [], - type: 'operator', - sortable: true, - }; - dynamicColumns.push(operatorColumn); - } + const operatorColumn: DynamicColumn = { + key: operator, + data: [], + type: 'operator', + sortable: true, + }; + dynamicColumns.push(operatorColumn); } }); return dynamicColumns; }; -const getQueryOperator = ( - queryData: IBuilderQuery[], - currentQueryName: string, -): string => { - const builderQuery = queryData.find((q) => q.queryName === currentQueryName); - - return builderQuery ? builderQuery.aggregateOperator : ''; -}; - const fillEmptyRowCells = ( unusedColumnsKeys: Set, sourceColumns: DynamicColumns, @@ -182,7 +177,7 @@ const fillEmptyRowCells = ( const fillDataFromSeria = ( seria: SeriesItem, columns: DynamicColumns, - currentOperator: string, + currentQueryName: string, ): void => { const labelEntries = Object.entries(seria.labels); @@ -192,15 +187,13 @@ const fillDataFromSeria = ( ); columns.forEach((column) => { - if (isFormula(column.key as string)) return; - if (column.key === 'timestamp') { column.data.push(value.timestamp); unusedColumnsKeys.delete('timestamp'); return; } - if (column.key === currentOperator) { + if (currentQueryName === column.key) { column.data.push(parseFloat(value.value).toFixed(2)); unusedColumnsKeys.delete(column.key); return; @@ -237,20 +230,20 @@ const fillDataFromList = ( }); }; -const fillColumnsData: FillColumnData = (queryTableData, cols, query) => { +const fillColumnsData: FillColumnData = (queryTableData, cols) => { const fields = cols.filter((item) => item.type === 'field'); const operators = cols.filter((item) => item.type === 'operator'); const resultColumns = [...fields, ...operators]; queryTableData.forEach((currentQuery) => { - const currentOperator = getQueryOperator( - query.builder.queryData, - currentQuery.queryName, - ); + // const currentOperator = getQueryOperator( + // query.builder.queryData, + // currentQuery.queryName, + // ); if (currentQuery.series) { currentQuery.series.forEach((seria) => { - fillDataFromSeria(seria, resultColumns, currentOperator); + fillDataFromSeria(seria, resultColumns, currentQuery.queryName); }); } @@ -320,7 +313,6 @@ export const createTableColumnsFromQuery: CreateTableDataFromQuery = ({ const { filledDynamicColumns, rowsLength } = fillColumnsData( queryTableData, dynamicColumns, - query, ); const dataSource = generateData(filledDynamicColumns, rowsLength); diff --git a/frontend/src/lib/toCapitalize.ts b/frontend/src/lib/toCapitalize.ts index a42a7336bf..7692dbf246 100644 --- a/frontend/src/lib/toCapitalize.ts +++ b/frontend/src/lib/toCapitalize.ts @@ -1,2 +1,5 @@ -export const toCapitalize = (str: string): string => - str[0].toUpperCase() + str.slice(1); +export const toCapitalize = (str: string): string => { + if (!str) return ''; + + return str[0].toUpperCase() + str.slice(1); +}; From b8aba4f935142437aedf8a3ab3df0f1f32105d5c Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Wed, 5 Jul 2023 10:34:07 +0530 Subject: [PATCH 23/29] fix: alert evaluation params and query (#3010) * fix: alert evaluation params and query 1. Update the rate query to not generate intermediary +inf value when the denominator is zero 2. Adjust the start and end time to incorporate data in movement 3. Round the start and end to minute 4. Add log to find the exact query that triggered alert for troubleshooting ; * chore: fix query builder tests --- .../app/metrics/query_builder.go | 2 +- .../app/metrics/v3/query_builder.go | 2 +- .../app/metrics/v3/query_builder_test.go | 2 +- pkg/query-service/rules/thresholdRule.go | 39 +++++++++++++------ 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/pkg/query-service/app/metrics/query_builder.go b/pkg/query-service/app/metrics/query_builder.go index 16620bf7cc..435e011dbd 100644 --- a/pkg/query-service/app/metrics/query_builder.go +++ b/pkg/query-service/app/metrics/query_builder.go @@ -45,7 +45,7 @@ var AggregateOperatorToSQLFunc = map[model.AggregateOperator]string{ } // See https://github.com/SigNoz/signoz/issues/2151#issuecomment-1467249056 -var rateWithoutNegative = `if (runningDifference(value) < 0 OR runningDifference(ts) < 0, nan, runningDifference(value)/runningDifference(ts))` +var rateWithoutNegative = `if (runningDifference(value) < 0 OR runningDifference(ts) <= 0, nan, runningDifference(value)/runningDifference(ts))` var SupportedFunctions = []string{"exp", "log", "ln", "exp2", "log2", "exp10", "log10", "sqrt", "cbrt", "erf", "erfc", "lgamma", "tgamma", "sin", "cos", "tan", "asin", "acos", "atan", "degrees", "radians"} diff --git a/pkg/query-service/app/metrics/v3/query_builder.go b/pkg/query-service/app/metrics/v3/query_builder.go index 780fbd3b89..f7e3956cca 100644 --- a/pkg/query-service/app/metrics/v3/query_builder.go +++ b/pkg/query-service/app/metrics/v3/query_builder.go @@ -44,7 +44,7 @@ var aggregateOperatorToSQLFunc = map[v3.AggregateOperator]string{ } // See https://github.com/SigNoz/signoz/issues/2151#issuecomment-1467249056 -var rateWithoutNegative = `if (runningDifference(value) < 0 OR runningDifference(ts) < 0, nan, runningDifference(value)/runningDifference(ts))` +var rateWithoutNegative = `if (runningDifference(value) < 0 OR runningDifference(ts) <= 0, nan, runningDifference(value)/runningDifference(ts))` // buildMetricsTimeSeriesFilterQuery builds the sub-query to be used for filtering // timeseries based on search criteria diff --git a/pkg/query-service/app/metrics/v3/query_builder_test.go b/pkg/query-service/app/metrics/v3/query_builder_test.go index b07a788855..7319236254 100644 --- a/pkg/query-service/app/metrics/v3/query_builder_test.go +++ b/pkg/query-service/app/metrics/v3/query_builder_test.go @@ -238,7 +238,7 @@ func TestBuildQueryOperators(t *testing.T) { func TestBuildQueryXRate(t *testing.T) { t.Run("TestBuildQueryXRate", func(t *testing.T) { - tmpl := `SELECT ts, %s(value) as value FROM (SELECT ts, if (runningDifference(value) < 0 OR runningDifference(ts) < 0, nan, runningDifference(value)/runningDifference(ts))as value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 0 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 GLOBAL INNER JOIN (SELECT fingerprint FROM signoz_metrics.distributed_time_series_v2 WHERE metric_name = 'name') as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND timestamp_ms >= 1650991982000 AND timestamp_ms <= 1651078382000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WHERE isNaN(value) = 0) GROUP BY ts ORDER BY ts` + tmpl := `SELECT ts, %s(value) as value FROM (SELECT ts, if (runningDifference(value) < 0 OR runningDifference(ts) <= 0, nan, runningDifference(value)/runningDifference(ts))as value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 0 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 GLOBAL INNER JOIN (SELECT fingerprint FROM signoz_metrics.distributed_time_series_v2 WHERE metric_name = 'name') as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND timestamp_ms >= 1650991982000 AND timestamp_ms <= 1651078382000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WHERE isNaN(value) = 0) GROUP BY ts ORDER BY ts` cases := []struct { aggregateOperator v3.AggregateOperator diff --git a/pkg/query-service/rules/thresholdRule.go b/pkg/query-service/rules/thresholdRule.go index 39d0aa0cad..f6e79a1643 100644 --- a/pkg/query-service/rules/thresholdRule.go +++ b/pkg/query-service/rules/thresholdRule.go @@ -22,7 +22,6 @@ import ( querytemplate "go.signoz.io/signoz/pkg/query-service/utils/queryTemplate" "go.signoz.io/signoz/pkg/query-service/utils/times" "go.signoz.io/signoz/pkg/query-service/utils/timestamp" - "go.signoz.io/signoz/pkg/query-service/utils/value" logsv3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3" metricsv3 "go.signoz.io/signoz/pkg/query-service/app/metrics/v3" @@ -327,7 +326,7 @@ func (r *ThresholdRule) SendAlerts(ctx context.Context, ts time.Time, resendDela } func (r *ThresholdRule) CheckCondition(v float64) bool { - if value.IsNaN(v) { + if math.IsNaN(v) { zap.S().Debugf("msg:", "found NaN in rule condition", "\t rule name:", r.Name()) return false } @@ -355,21 +354,37 @@ func (r *ThresholdRule) CheckCondition(v float64) bool { func (r *ThresholdRule) prepareQueryRange(ts time.Time) *v3.QueryRangeParamsV3 { // todo(amol): add 30 seconds to evalWindow for rate calc + // todo(srikanthccv): make this configurable + // 2 minutes is reasonable time to wait for data to be available + // 60 seconds (SDK) + 10 seconds (batch) + rest for n/w + serialization + write to disk etc.. + start := ts.Add(-time.Duration(r.evalWindow)).UnixMilli() - 2*60*1000 + end := ts.UnixMilli() - 2*60*1000 + + // round to minute otherwise we could potentially miss data + start = start - (start % (60 * 1000)) + end = end - (end % (60 * 1000)) + if r.ruleCondition.QueryType() == v3.QueryTypeClickHouseSQL { return &v3.QueryRangeParamsV3{ - Start: ts.Add(-time.Duration(r.evalWindow)).UnixMilli(), - End: ts.UnixMilli(), - Step: 30, + Start: start, + End: end, + Step: 60, CompositeQuery: r.ruleCondition.CompositeQuery, Variables: make(map[string]interface{}, 0), } } + if r.ruleCondition.CompositeQuery != nil && r.ruleCondition.CompositeQuery.BuilderQueries != nil { + for _, q := range r.ruleCondition.CompositeQuery.BuilderQueries { + q.StepInterval = 60 + } + } + // default mode return &v3.QueryRangeParamsV3{ - Start: ts.Add(-time.Duration(r.evalWindow)).UnixMilli(), - End: ts.UnixMilli(), - Step: 30, + Start: start, + End: end, + Step: 60, CompositeQuery: r.ruleCondition.CompositeQuery, } } @@ -476,7 +491,7 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer } } - if value.IsNaN(sample.Point.V) { + if math.IsNaN(sample.Point.V) { continue } @@ -521,7 +536,7 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer // we skip the first record to support rate cases correctly // improvement(amol): explore approaches to limit this only for // rate uses cases - if exists, _ := skipFirstRecord[labelHash]; exists { + if exists := skipFirstRecord[labelHash]; exists { resultMap[labelHash] = sample } else { // looks like the first record for this label combo, skip it @@ -545,7 +560,9 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer result = append(result, sample) } } - zap.S().Debugf("ruleid:", r.ID(), "\t result (found alerts):", len(result)) + if len(result) != 0 { + zap.S().Infof("For rule %s, with ClickHouseQuery %s, found %d alerts", r.ID(), query, len(result)) + } return result, nil } From 2722538e82d683bc8e2b9f69b8205d1a93f0e106 Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Wed, 5 Jul 2023 11:20:46 +0530 Subject: [PATCH 24/29] Fix/handle hypen attributes (#3023) * fix: handle attributes with hypen `-` * test: update tests * fix: only use backticks on columns orderby --- .../app/traces/v3/query_builder.go | 17 +++-- .../app/traces/v3/query_builder_test.go | 64 +++++++++++-------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/pkg/query-service/app/traces/v3/query_builder.go b/pkg/query-service/app/traces/v3/query_builder.go index abf38b6b40..49572241d9 100644 --- a/pkg/query-service/app/traces/v3/query_builder.go +++ b/pkg/query-service/app/traces/v3/query_builder.go @@ -148,7 +148,9 @@ func buildTracesFilterQuery(fs *v3.FilterSet, keys map[string]v3.AttributeKey) ( return "", fmt.Errorf("invalid value for key %s: %v", item.Key.Key, err) } } - fmtVal = utils.ClickHouseFormattedValue(val) + if val != nil { + fmtVal = utils.ClickHouseFormattedValue(val) + } if operator, ok := tracesOperatorMappingV3[item.Operator]; ok { switch item.Operator { case v3.FilterOperatorContains, v3.FilterOperatorNotContains: @@ -356,7 +358,7 @@ func groupBy(tags ...string) string { func groupByAttributeKeyTags(keys map[string]v3.AttributeKey, tags ...v3.AttributeKey) string { groupTags := []string{} for _, tag := range tags { - groupTags = append(groupTags, tag.Key) + groupTags = append(groupTags, fmt.Sprintf("`%s`", tag.Key)) } return groupBy(groupTags...) } @@ -378,10 +380,10 @@ func orderBy(panelType v3.PanelType, items []v3.OrderBy, tags []string, keys map for _, tag := range tags { if item, ok := itemsLookup[tag]; ok { - orderBy = append(orderBy, fmt.Sprintf("%s %s", item.ColumnName, item.Order)) + orderBy = append(orderBy, fmt.Sprintf("`%s` %s", item.ColumnName, item.Order)) addedToOrderBy[item.ColumnName] = true } else { - orderBy = append(orderBy, fmt.Sprintf("%s ASC", tag)) + orderBy = append(orderBy, fmt.Sprintf("`%s` ASC", tag)) } } @@ -401,7 +403,12 @@ func orderBy(panelType v3.PanelType, items []v3.OrderBy, tags []string, keys map if !addedToOrderBy[item.ColumnName] { attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn} name := getColumnName(attr, keys) - orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order)) + + if item.IsColumn { + orderBy = append(orderBy, fmt.Sprintf("`%s` %s", name, item.Order)) + } else { + orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order)) + } } } } diff --git a/pkg/query-service/app/traces/v3/query_builder_test.go b/pkg/query-service/app/traces/v3/query_builder_test.go index 97c739d8c1..023a636d12 100644 --- a/pkg/query-service/app/traces/v3/query_builder_test.go +++ b/pkg/query-service/app/traces/v3/query_builder_test.go @@ -340,7 +340,7 @@ var testOrderBy = []struct { }, }, Tags: []string{"name"}, - Result: []string{"name asc", "value desc"}, + Result: []string{"`name` asc", "value desc"}, }, { Name: "Test 2", @@ -356,7 +356,7 @@ var testOrderBy = []struct { }, }, Tags: []string{"name", "bytes"}, - Result: []string{"name asc", "bytes asc"}, + Result: []string{"`name` asc", "`bytes` asc"}, }, { Name: "Test 3", @@ -376,7 +376,7 @@ var testOrderBy = []struct { }, }, Tags: []string{"name", "bytes"}, - Result: []string{"name asc", "bytes asc", "value asc"}, + Result: []string{"`name` asc", "`bytes` asc", "value asc"}, }, { Name: "Test 4", @@ -399,19 +399,27 @@ var testOrderBy = []struct { }, }, Tags: []string{"name", "bytes"}, - Result: []string{"name asc", "bytes asc", "stringTagMap['response_time'] desc"}, + Result: []string{"`name` asc", "`bytes` asc", "stringTagMap['response_time'] desc"}, }, { - Name: "Test 4", + Name: "Test 5", PanelType: v3.PanelTypeList, Items: []v3.OrderBy{ { ColumnName: "name", Order: "asc", + Key: "name", + Type: v3.AttributeKeyTypeTag, + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, }, { ColumnName: "bytes", Order: "asc", + Key: "bytes", + Type: v3.AttributeKeyTypeTag, + DataType: v3.AttributeKeyDataTypeString, + IsColumn: true, }, { ColumnName: "response_time", @@ -419,7 +427,7 @@ var testOrderBy = []struct { }, }, Tags: []string{}, - Result: []string{"name asc", "bytes asc", "stringTagMap['response_time'] desc"}, + Result: []string{"`name` asc", "`bytes` asc", "stringTagMap['response_time'] desc"}, }, } @@ -630,8 +638,8 @@ var testBuildTracesQueryData = []struct { "toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['http.method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " + - "AND has(stringTagMap, 'http.method') group by http.method,ts " + - "order by http.method ASC,ts", + "AND has(stringTagMap, 'http.method') group by `http.method`,ts " + + "order by `http.method` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -662,8 +670,8 @@ var testBuildTracesQueryData = []struct { "toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " + - "AND has(stringTagMap, 'method') AND has(resourceTagsMap, 'x') group by method,x,ts " + - "order by method ASC,x ASC,ts", + "AND has(stringTagMap, 'method') AND has(resourceTagsMap, 'x') group by `method`,`x`,ts " + + "order by `method` ASC,`x` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -690,8 +698,8 @@ var testBuildTracesQueryData = []struct { "from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' " + - "AND has(stringTagMap, 'method') group by method,ts " + - "order by method ASC,ts", + "AND has(stringTagMap, 'method') group by `method`,ts " + + "order by `method` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -718,8 +726,8 @@ var testBuildTracesQueryData = []struct { "from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' " + - "AND has(stringTagMap, 'method') group by method,ts " + - "order by method ASC,ts", + "AND has(stringTagMap, 'method') group by `method`,ts " + + "order by `method` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -746,8 +754,8 @@ var testBuildTracesQueryData = []struct { "from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' " + - "AND has(stringTagMap, 'method') group by method,ts " + - "order by method ASC,ts", + "AND has(stringTagMap, 'method') group by `method`,ts " + + "order by `method` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -774,8 +782,8 @@ var testBuildTracesQueryData = []struct { "from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' " + - "AND has(stringTagMap, 'method') group by method,ts " + - "order by method ASC,ts", + "AND has(stringTagMap, 'method') group by `method`,ts " + + "order by `method` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -798,8 +806,8 @@ var testBuildTracesQueryData = []struct { "quantile(0.05)(bytes) as value " + "from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + - "AND has(stringTagMap, 'method') group by method,ts " + - "order by method ASC,ts", + "AND has(stringTagMap, 'method') group by `method`,ts " + + "order by `method` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -820,7 +828,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, stringTagMap['method'] as `method`" + ", sum(bytes)/60 as value from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " AND has(stringTagMap, 'method') group by method,ts order by method ASC,ts", + " AND has(stringTagMap, 'method') group by `method`,ts order by `method` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -841,8 +849,8 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, stringTagMap['method'] as `method`" + ", count(numberTagMap['bytes'])/60 as value " + "from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + - "AND has(stringTagMap, 'method') group by method,ts " + - "order by method ASC,ts", + "AND has(stringTagMap, 'method') group by `method`,ts " + + "order by `method` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -864,8 +872,8 @@ var testBuildTracesQueryData = []struct { "stringTagMap['method'] as `method`, " + "sum(numberTagMap['bytes'])/60 as value " + "from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + - "AND has(stringTagMap, 'method') group by method,ts " + - "order by method ASC,ts", + "AND has(stringTagMap, 'method') group by `method`,ts " + + "order by `method` ASC,ts", PanelType: v3.PanelTypeGraph, }, { @@ -984,7 +992,7 @@ var testBuildTracesQueryData = []struct { }, ExpectedQuery: "SELECT timestamp as timestamp_datetime, spanID, traceID," + " name as `name` from signoz_traces.distributed_signoz_index_v2 where " + - "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') order by name ASC", + "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') order by `name` ASC", PanelType: v3.PanelTypeList, }, { @@ -1006,8 +1014,8 @@ var testBuildTracesQueryData = []struct { }, ExpectedQuery: "SELECT timestamp as timestamp_datetime, spanID, traceID," + " name as `name` from signoz_traces.distributed_signoz_index_v2 where " + - "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " AND stringTagMap['method'] = 'GET' order by name ASC", + "(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + + " AND stringTagMap['method'] = 'GET' order by `name` ASC", PanelType: v3.PanelTypeList, }, { From 5540692500152d3b4a57485250af7af8222a372a Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Wed, 5 Jul 2023 10:21:22 +0300 Subject: [PATCH 25/29] feat: add the list view for the traces explorer (#2947) * feat: add dynamic table based on query * feat: add the list view for the traces explorer * fix: fix the options menu * feat: update the list view columns config for the traces explorer * feat: fix columns for the list view for the traces explorer page * feat: update customization columns for the list view from the traces explorer * feat: add error msg for the list view, fix creating data for the table --------- Co-authored-by: Yevhen Shevchenko Co-authored-by: Nazarenko19 Co-authored-by: Vishal Sharma --- .../container/OptionsMenu/useOptionsMenu.ts | 25 +++- .../src/container/TimeSeriesView/index.tsx | 14 +- .../TracesExplorer/ListView/configs.tsx | 11 ++ .../TracesExplorer/ListView/index.tsx | 133 ++++++++++++++++++ .../TracesExplorer/ListView/styles.ts | 17 +++ .../TracesExplorer/ListView/utils.tsx | 97 +++++++++++++ .../TracesExplorer/TracesView/index.tsx | 4 +- .../lib/query/createTableColumnsFromQuery.ts | 24 ++-- frontend/src/pages/TracesExplorer/index.tsx | 18 +-- frontend/src/pages/TracesExplorer/utils.tsx | 7 + frontend/src/types/api/logs/log.ts | 1 + 11 files changed, 318 insertions(+), 33 deletions(-) create mode 100644 frontend/src/container/TracesExplorer/ListView/configs.tsx create mode 100644 frontend/src/container/TracesExplorer/ListView/index.tsx create mode 100644 frontend/src/container/TracesExplorer/ListView/styles.ts create mode 100644 frontend/src/container/TracesExplorer/ListView/utils.tsx diff --git a/frontend/src/container/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts index ffec17e388..13db2516dc 100644 --- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts +++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts @@ -1,6 +1,7 @@ import { RadioChangeEvent } from 'antd'; import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; import { QueryBuilderKeys } from 'constants/queryBuilder'; +import { useNotifications } from 'hooks/useNotifications'; import useUrlQueryData from 'hooks/useUrlQueryData'; import { useCallback, useEffect, useMemo } from 'react'; import { useQuery } from 'react-query'; @@ -28,6 +29,8 @@ const useOptionsMenu = ({ aggregateOperator, initialOptions = {}, }: UseOptionsMenuProps): UseOptionsMenu => { + const { notifications } = useNotifications(); + const { query: optionsQuery, queryData: optionsQueryData, @@ -91,14 +94,22 @@ const useOptionsMenu = ({ const handleRemoveSelectedColumn = useCallback( (columnKey: string) => { - redirectWithOptionsData({ - ...defaultOptionsQuery, - selectColumns: optionsQueryData?.selectColumns?.filter( - ({ id }) => id !== columnKey, - ), - }); + const newSelectedColumns = optionsQueryData?.selectColumns?.filter( + ({ id }) => id !== columnKey, + ); + + if (!newSelectedColumns.length) { + notifications.error({ + message: 'There must be at least one selected column', + }); + } else { + redirectWithOptionsData({ + ...defaultOptionsQuery, + selectColumns: newSelectedColumns, + }); + } }, - [optionsQueryData, redirectWithOptionsData], + [optionsQueryData, notifications, redirectWithOptionsData], ); const handleFormatChange = useCallback( diff --git a/frontend/src/container/TimeSeriesView/index.tsx b/frontend/src/container/TimeSeriesView/index.tsx index 1e47226b01..b5a15e7751 100644 --- a/frontend/src/container/TimeSeriesView/index.tsx +++ b/frontend/src/container/TimeSeriesView/index.tsx @@ -1,7 +1,10 @@ -import { initialQueriesMap } from 'constants/queryBuilder'; +import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; +import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import useUrlQueryData from 'hooks/useUrlQueryData'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { DataSource } from 'types/common/queryBuilder'; @@ -19,10 +22,15 @@ function TimeSeriesViewContainer({ GlobalReducer >((state) => state.globalTime); + const { queryData: panelTypeParam } = useUrlQueryData( + PANEL_TYPES_QUERY, + PANEL_TYPES.TIME_SERIES, + ); + const { data, isLoading, isError } = useGetQueryRange( { query: stagedQuery || initialQueriesMap[dataSource], - graphType: 'graph', + graphType: panelTypeParam, selectedTime: 'GLOBAL_TIME', globalSelectedInterval: globalSelectedTime, params: { @@ -37,7 +45,7 @@ function TimeSeriesViewContainer({ minTime, stagedQuery, ], - enabled: !!stagedQuery, + enabled: !!stagedQuery && panelTypeParam === PANEL_TYPES.TIME_SERIES, }, ); diff --git a/frontend/src/container/TracesExplorer/ListView/configs.tsx b/frontend/src/container/TracesExplorer/ListView/configs.tsx new file mode 100644 index 0000000000..3b05ed8169 --- /dev/null +++ b/frontend/src/container/TracesExplorer/ListView/configs.tsx @@ -0,0 +1,11 @@ +import { DEFAULT_PER_PAGE_OPTIONS } from 'hooks/queryPagination'; + +export const defaultSelectedColumns: string[] = [ + 'name', + 'serviceName', + 'responseStatusCode', + 'httpMethod', + 'durationNano', +]; + +export const PER_PAGE_OPTIONS: number[] = [10, ...DEFAULT_PER_PAGE_OPTIONS]; diff --git a/frontend/src/container/TracesExplorer/ListView/index.tsx b/frontend/src/container/TracesExplorer/ListView/index.tsx new file mode 100644 index 0000000000..674184f749 --- /dev/null +++ b/frontend/src/container/TracesExplorer/ListView/index.tsx @@ -0,0 +1,133 @@ +import { ColumnsType } from 'antd/es/table'; +import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useOptionsMenu } from 'container/OptionsMenu'; +import { QueryTable } from 'container/QueryTable'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { Pagination, URL_PAGINATION } from 'hooks/queryPagination'; +import useUrlQueryData from 'hooks/useUrlQueryData'; +import history from 'lib/history'; +import { RowData } from 'lib/query/createTableColumnsFromQuery'; +import { HTMLAttributes, memo, useCallback, useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { DataSource } from 'types/common/queryBuilder'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import TraceExplorerControls from '../Controls'; +import { defaultSelectedColumns, PER_PAGE_OPTIONS } from './configs'; +import { Container, ErrorText, tableStyles } from './styles'; +import { getTraceLink, modifyColumns, transformDataWithDate } from './utils'; + +function ListView(): JSX.Element { + const { stagedQuery, panelType } = useQueryBuilder(); + + const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const { options, config } = useOptionsMenu({ + dataSource: DataSource.TRACES, + aggregateOperator: 'count', + initialOptions: { + selectColumns: defaultSelectedColumns, + }, + }); + + const { queryData: paginationQueryData } = useUrlQueryData( + URL_PAGINATION, + ); + + const { data, isFetching, isError } = useGetQueryRange( + { + query: stagedQuery || initialQueriesMap.traces, + graphType: panelType || PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + globalSelectedInterval: globalSelectedTime, + params: { + dataSource: 'traces', + }, + tableParams: { + pagination: paginationQueryData, + selectColumns: options?.selectColumns, + }, + }, + { + queryKey: [ + REACT_QUERY_KEY.GET_QUERY_RANGE, + globalSelectedTime, + maxTime, + minTime, + stagedQuery, + panelType, + paginationQueryData, + options?.selectColumns, + ], + enabled: !!stagedQuery && panelType === PANEL_TYPES.LIST, + }, + ); + + const dataLength = + data?.payload?.data?.newResult?.data?.result[0]?.list?.length; + const totalCount = useMemo(() => dataLength || 0, [dataLength]); + + const queryTableDataResult = data?.payload.data.newResult.data.result; + const queryTableData = useMemo(() => queryTableDataResult || [], [ + queryTableDataResult, + ]); + + const transformedQueryTableData = useMemo( + () => transformDataWithDate(queryTableData), + [queryTableData], + ); + + const handleModifyColumns = useCallback( + (columns: ColumnsType) => + modifyColumns(columns, options?.selectColumns || []), + [options?.selectColumns], + ); + + const handleRow = useCallback( + (record: RowData): HTMLAttributes => ({ + onClick: (event): void => { + event.preventDefault(); + event.stopPropagation(); + if (event.metaKey || event.ctrlKey) { + window.open(getTraceLink(record), '_blank'); + } else { + history.push(getTraceLink(record)); + } + }, + }), + [], + ); + + return ( + + + + {isError && {data?.error || 'Something went wrong'}} + + {!isError && ( + + )} + + ); +} + +export default memo(ListView); diff --git a/frontend/src/container/TracesExplorer/ListView/styles.ts b/frontend/src/container/TracesExplorer/ListView/styles.ts new file mode 100644 index 0000000000..834dd5209e --- /dev/null +++ b/frontend/src/container/TracesExplorer/ListView/styles.ts @@ -0,0 +1,17 @@ +import { Typography } from 'antd'; +import { CSSProperties } from 'react'; +import styled from 'styled-components'; + +export const tableStyles: CSSProperties = { + cursor: 'pointer', +}; + +export const Container = styled.div` + display: flex; + flex-direction: column; + gap: 15px; +`; + +export const ErrorText = styled(Typography)` + text-align: center; +`; diff --git a/frontend/src/container/TracesExplorer/ListView/utils.tsx b/frontend/src/container/TracesExplorer/ListView/utils.tsx new file mode 100644 index 0000000000..59f36a88ab --- /dev/null +++ b/frontend/src/container/TracesExplorer/ListView/utils.tsx @@ -0,0 +1,97 @@ +import { Tag } from 'antd'; +import { ColumnsType } from 'antd/es/table'; +import Typography from 'antd/es/typography/Typography'; +import ROUTES from 'constants/routes'; +import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util'; +import { formUrlParams } from 'container/TraceDetail/utils'; +import dayjs from 'dayjs'; +import { RowData } from 'lib/query/createTableColumnsFromQuery'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { QueryDataV3 } from 'types/api/widgets/getQuery'; + +export const transformDataWithDate = (data: QueryDataV3[]): QueryDataV3[] => + data.map((query) => ({ + ...query, + list: + query?.list?.map((listItem) => ({ + ...listItem, + data: { + ...listItem?.data, + date: listItem?.timestamp, + }, + })) || null, + })); + +export const modifyColumns = ( + columns: ColumnsType, + selectedColumns: BaseAutocompleteData[], +): ColumnsType => { + const initialColumns = columns.filter(({ key }) => { + let isValidColumn = true; + + const checkIsExistColumnByKey = (attributeKey: string): boolean => + !selectedColumns.find(({ key }) => key === attributeKey) && + attributeKey === key; + + const isSelectedSpanId = checkIsExistColumnByKey('spanID'); + const isSelectedTraceId = checkIsExistColumnByKey('traceID'); + + if (isSelectedSpanId || isSelectedTraceId || key === 'date') + isValidColumn = false; + + return isValidColumn; + }); + + const dateColumn = columns.find(({ key }) => key === 'date'); + + if (dateColumn) { + initialColumns.unshift(dateColumn); + } + + return initialColumns.map((column) => { + const key = column.key as string; + + const getHttpMethodOrStatus = (value: string): JSX.Element => { + if (value === 'N/A') { + return {value}; + } + + return {value}; + }; + + if (key === 'durationNano') { + return { + ...column, + render: (duration: string): JSX.Element => ( + {getMs(duration)}ms + ), + }; + } + + if (key === 'httpMethod' || key === 'responseStatusCode') { + return { + ...column, + render: getHttpMethodOrStatus, + }; + } + + if (key === 'date') { + return { + ...column, + render: (date: string): JSX.Element => { + const day = dayjs(date); + return {day.format('YYYY/MM/DD HH:mm:ss')}; + }, + }; + } + + return column; + }); +}; + +export const getTraceLink = (record: RowData): string => + `${ROUTES.TRACE}/${record.traceID}${formUrlParams({ + spanId: record.spanID, + levelUp: 0, + levelDown: 0, + })}`; diff --git a/frontend/src/container/TracesExplorer/TracesView/index.tsx b/frontend/src/container/TracesExplorer/TracesView/index.tsx index e4ef1e5ec5..0d1eae9d02 100644 --- a/frontend/src/container/TracesExplorer/TracesView/index.tsx +++ b/frontend/src/container/TracesExplorer/TracesView/index.tsx @@ -6,7 +6,7 @@ import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { Pagination, URL_PAGINATION } from 'hooks/queryPagination'; import useUrlQueryData from 'hooks/useUrlQueryData'; -import { useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { GlobalReducer } from 'types/reducer/globalTime'; @@ -84,4 +84,4 @@ function TracesView(): JSX.Element { ); } -export default TracesView; +export default memo(TracesView); diff --git a/frontend/src/lib/query/createTableColumnsFromQuery.ts b/frontend/src/lib/query/createTableColumnsFromQuery.ts index 168261238f..653a859ba8 100644 --- a/frontend/src/lib/query/createTableColumnsFromQuery.ts +++ b/frontend/src/lib/query/createTableColumnsFromQuery.ts @@ -23,7 +23,7 @@ type DynamicColumn = { key: keyof RowData; data: (string | number)[]; type: 'field' | 'operator'; - sortable: boolean; + // sortable: boolean; }; type DynamicColumns = DynamicColumn[]; @@ -91,15 +91,15 @@ const createLabels = ( ): void => { if (isColumnExist(label as string, dynamicColumns)) return; - const labelValue = labels[label]; + // const labelValue = labels[label]; - const isNumber = !Number.isNaN(parseFloat(String(labelValue))); + // const isNumber = !Number.isNaN(parseFloat(String(labelValue))); const fieldObj: DynamicColumn = { key: label as string, data: [], type: 'field', - sortable: isNumber, + // sortable: isNumber, }; dynamicColumns.push(fieldObj); @@ -127,7 +127,7 @@ const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => { key: 'timestamp', data: [], type: 'field', - sortable: true, + // sortable: true, }); } @@ -148,7 +148,7 @@ const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => { key: operator, data: [], type: 'operator', - sortable: true, + // sortable: true, }; dynamicColumns.push(operatorColumn); } @@ -220,8 +220,8 @@ const fillDataFromList = ( Object.keys(listItem.data).forEach((label) => { if (column.key === label) { - if (listItem.data[label as ListItemKey]) { - column.data.push(listItem.data[label as ListItemKey] as string | number); + if (listItem.data[label as ListItemKey] !== '') { + column.data.push(listItem.data[label as ListItemKey].toString()); } else { column.data.push('N/A'); } @@ -291,10 +291,10 @@ const generateTableColumns = ( dataIndex: item.key, key: item.key, title: prepareColumnTitle(item.key as string), - sorter: item.sortable - ? (a: RowData, b: RowData): number => - (a[item.key] as number) - (b[item.key] as number) - : false, + // sorter: item.sortable + // ? (a: RowData, b: RowData): number => + // (a[item.key] as number) - (b[item.key] as number) + // : false, }; return [...acc, column]; diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index d6073fc466..c70f318dc0 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -43,6 +43,15 @@ function TracesExplorer(): JSX.Element { [currentQuery], ); + const isGroupByExist = useMemo(() => { + const groupByCount: number = currentQuery.builder.queryData.reduce( + (acc, query) => acc + query.groupBy.length, + 0, + ); + + return groupByCount > 0; + }, [currentQuery]); + const defaultQuery = useMemo( () => updateAllQueriesOperators( @@ -53,15 +62,6 @@ function TracesExplorer(): JSX.Element { [updateAllQueriesOperators], ); - const isGroupByExist = useMemo(() => { - const groupByCount: number = currentQuery.builder.queryData.reduce( - (acc, query) => acc + query.groupBy.length, - 0, - ); - - return groupByCount > 0; - }, [currentQuery]); - const tabsItems = getTabsItems({ isListViewDisabled: isMultipleQueries || isGroupByExist, }); diff --git a/frontend/src/pages/TracesExplorer/utils.tsx b/frontend/src/pages/TracesExplorer/utils.tsx index 116a125c83..2c0143dadc 100644 --- a/frontend/src/pages/TracesExplorer/utils.tsx +++ b/frontend/src/pages/TracesExplorer/utils.tsx @@ -1,6 +1,7 @@ import { TabsProps } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; import TimeSeriesView from 'container/TimeSeriesView'; +import ListView from 'container/TracesExplorer/ListView'; import TracesView from 'container/TracesExplorer/TracesView'; import { DataSource } from 'types/common/queryBuilder'; @@ -11,6 +12,12 @@ interface GetTabsItemsProps { export const getTabsItems = ({ isListViewDisabled, }: GetTabsItemsProps): TabsProps['items'] => [ + { + label: 'List View', + key: PANEL_TYPES.LIST, + children: , + disabled: isListViewDisabled, + }, { label: 'Traces', key: PANEL_TYPES.TRACE, diff --git a/frontend/src/types/api/logs/log.ts b/frontend/src/types/api/logs/log.ts index ef61ba9871..eb862daa9c 100644 --- a/frontend/src/types/api/logs/log.ts +++ b/frontend/src/types/api/logs/log.ts @@ -1,4 +1,5 @@ export interface ILog { + date: string; timestamp: number; id: string; traceId: string; From b1c1a95e290b8254ba1f59014fd7c41cac710ee3 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Wed, 5 Jul 2023 13:46:43 +0530 Subject: [PATCH 26/29] feat: update default logs page size (#3030) --- frontend/src/store/reducers/logs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/store/reducers/logs.ts b/frontend/src/store/reducers/logs.ts index 6572834fde..0d0a69a1a4 100644 --- a/frontend/src/store/reducers/logs.ts +++ b/frontend/src/store/reducers/logs.ts @@ -39,7 +39,7 @@ const initialState: ILogsReducer = { parsedQuery: [], }, logs: [], - logLinesPerPage: 25, + logLinesPerPage: 200, linesPerRow: 2, viewMode: 'raw', idEnd: '', From 8363dadd8d1b5f5730ef80e983f429eda8989f16 Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:50:20 +0300 Subject: [PATCH 27/29] fix: resolve the list view issues (#3020) * feat: add dynamic table based on query * feat: add the list view for the traces explorer * fix: fix the options menu * feat: update the list view columns config for the traces explorer * feat: fix columns for the list view for the traces explorer page * feat: update customization columns for the list view from the traces explorer * feat: add error msg for the list view, fix creating data for the table * fix: resolve the list view issues * fix: update the date column for the list view * fix: remove additional filter title for the list view * fix: add initial orderBy filter for the list view --------- Co-authored-by: Yevhen Shevchenko Co-authored-by: Nazarenko19 Co-authored-by: Vishal Sharma --- .../OptionsMenu/AddColumnField/index.tsx | 20 +++----- frontend/src/container/OptionsMenu/types.ts | 3 +- .../container/OptionsMenu/useOptionsMenu.ts | 17 ++++--- frontend/src/container/OptionsMenu/utils.ts | 10 +++- .../QueryBuilder/components/Query/Query.tsx | 51 +++++++++++-------- .../AggregatorFilter.intefaces.ts | 5 +- .../AggregatorFilter/AggregatorFilter.tsx | 4 +- .../filters/OrderByFilter/OrderByFilter.tsx | 5 +- .../TimeSeriesView/TimeSeriesView.tsx | 2 +- .../src/container/TimeSeriesView/index.tsx | 14 ++--- .../TracesExplorer/ListView/index.tsx | 3 +- .../TracesExplorer/ListView/styles.ts | 4 ++ .../TracesExplorer/ListView/utils.tsx | 5 +- .../hooks/queryBuilder/useQueryOperations.ts | 13 +++-- frontend/src/pages/TracesExplorer/index.tsx | 29 +++++++---- 15 files changed, 108 insertions(+), 77 deletions(-) diff --git a/frontend/src/container/OptionsMenu/AddColumnField/index.tsx b/frontend/src/container/OptionsMenu/AddColumnField/index.tsx index 77e1b19b6d..384dba8aaa 100644 --- a/frontend/src/container/OptionsMenu/AddColumnField/index.tsx +++ b/frontend/src/container/OptionsMenu/AddColumnField/index.tsx @@ -38,20 +38,12 @@ function AddColumnField({ config }: AddColumnFieldProps): JSX.Element | null { - {config.value.map((selectedValue: string) => { - const option = config?.options?.find( - ({ value }) => value === selectedValue, - ); - - return ( - - {option?.label} - config.onRemove(selectedValue)} - /> - - ); - })} + {config.value?.map(({ key, id }) => ( + + {key} + config.onRemove(id as string)} /> + + ))} ); } diff --git a/frontend/src/container/OptionsMenu/types.ts b/frontend/src/container/OptionsMenu/types.ts index eba54eadaf..f557e1dbe3 100644 --- a/frontend/src/container/OptionsMenu/types.ts +++ b/frontend/src/container/OptionsMenu/types.ts @@ -15,7 +15,8 @@ export interface InitialOptions export type OptionsMenuConfig = { format?: Pick; maxLines?: Pick; - addColumn?: Pick & { + addColumn?: Pick & { + value: BaseAutocompleteData[]; onRemove: (key: string) => void; }; }; diff --git a/frontend/src/container/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts index 13db2516dc..f39557f9f7 100644 --- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts +++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts @@ -63,15 +63,16 @@ const useOptionsMenu = ({ [initialOptions, attributeKeys], ); - const options = useMemo(() => getOptionsFromKeys(attributeKeys), [ - attributeKeys, - ]); - const selectedColumnKeys = useMemo( () => optionsQueryData?.selectColumns?.map(({ id }) => id) || [], [optionsQueryData], ); + const addColumnOptions = useMemo( + () => getOptionsFromKeys(attributeKeys, selectedColumnKeys), + [attributeKeys, selectedColumnKeys], + ); + const handleSelectedColumnsChange = useCallback( (value: string[]) => { const newSelectedColumnKeys = [ @@ -135,8 +136,8 @@ const useOptionsMenu = ({ const optionsMenuConfig: Required = useMemo( () => ({ addColumn: { - value: selectedColumnKeys || defaultOptionsQuery.selectColumns, - options: options || [], + value: optionsQueryData?.selectColumns || defaultOptionsQuery.selectColumns, + options: addColumnOptions || [], onChange: handleSelectedColumnsChange, onRemove: handleRemoveSelectedColumn, }, @@ -150,10 +151,10 @@ const useOptionsMenu = ({ }, }), [ - options, - selectedColumnKeys, + addColumnOptions, optionsQueryData?.maxLines, optionsQueryData?.format, + optionsQueryData?.selectColumns, handleSelectedColumnsChange, handleRemoveSelectedColumn, handleFormatChange, diff --git a/frontend/src/container/OptionsMenu/utils.ts b/frontend/src/container/OptionsMenu/utils.ts index e4fa3fc2b2..1a0b904ac5 100644 --- a/frontend/src/container/OptionsMenu/utils.ts +++ b/frontend/src/container/OptionsMenu/utils.ts @@ -3,12 +3,18 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe export const getOptionsFromKeys = ( keys: BaseAutocompleteData[], -): SelectProps['options'] => - keys.map(({ id, key }) => ({ + selectedKeys: (string | undefined)[], +): SelectProps['options'] => { + const options = keys.map(({ id, key }) => ({ label: key, value: id, })); + return options.filter( + ({ value }) => !selectedKeys.find((key) => key === value), + ); +}; + export const getInitialColumns = ( initialColumnTitles: string[], attributeKeys: BaseAutocompleteData[], diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index cda9b75609..d0c5f126a5 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -230,19 +230,21 @@ export const Query = memo(function Query({
- - - - - - - - - - + {panelType !== PANEL_TYPES.LIST && ( + + + + + + + + + + + )} ); } @@ -326,8 +328,11 @@ export const Query = memo(function Query({ @@ -362,14 +367,16 @@ export const Query = memo(function Query({ )} - - - + {panelType !== PANEL_TYPES.LIST && panelType !== PANEL_TYPES.TRACE && ( + + + + )} ); }); diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts index cff5c295df..35c5f0178a 100644 --- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts @@ -1,7 +1,8 @@ +import { AutoCompleteProps } from 'antd'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -export type AgregatorFilterProps = { - onChange: (value: BaseAutocompleteData) => void; +export type AgregatorFilterProps = Pick & { query: IBuilderQuery; + onChange: (value: BaseAutocompleteData) => void; }; diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx index a168d32d0c..2f8cf1ea90 100644 --- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx @@ -28,8 +28,9 @@ import { selectStyle } from '../QueryBuilderSearch/config'; import { AgregatorFilterProps } from './AggregatorFilter.intefaces'; export const AggregatorFilter = memo(function AggregatorFilter({ - onChange, query, + disabled, + onChange, }: AgregatorFilterProps): JSX.Element { const [optionsData, setOptionsData] = useState([]); const debouncedValue = useDebounce(query.aggregateAttribute.key, 300); @@ -119,6 +120,7 @@ export const AggregatorFilter = memo(function AggregatorFilter({ options={optionsData} value={value} onChange={handleChangeAttribute} + disabled={disabled} /> ); }); diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx index 96187ed6f6..f85c72dc34 100644 --- a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx @@ -18,6 +18,7 @@ import { getLabelFromValue, mapLabelValuePairs, orderByValueDelimiter, + transformToOrderByStringValues, } from './utils'; export function OrderByFilter({ @@ -25,7 +26,9 @@ export function OrderByFilter({ onChange, }: OrderByFilterProps): JSX.Element { const [searchText, setSearchText] = useState(''); - const [selectedValue, setSelectedValue] = useState([]); + const [selectedValue, setSelectedValue] = useState( + transformToOrderByStringValues(query.orderBy) || [], + ); const { data, isFetching } = useQuery( [QueryBuilderKeys.GET_AGGREGATE_KEYS, searchText], diff --git a/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx b/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx index 398823205f..ca01a0156c 100644 --- a/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx +++ b/frontend/src/container/TimeSeriesView/TimeSeriesView.tsx @@ -21,7 +21,7 @@ function TimeSeriesView({ }, ], }), - [data], + [data?.payload?.data?.result], ); return ( diff --git a/frontend/src/container/TimeSeriesView/index.tsx b/frontend/src/container/TimeSeriesView/index.tsx index b5a15e7751..5212334a4d 100644 --- a/frontend/src/container/TimeSeriesView/index.tsx +++ b/frontend/src/container/TimeSeriesView/index.tsx @@ -1,10 +1,7 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; -import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import useUrlQueryData from 'hooks/useUrlQueryData'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { DataSource } from 'types/common/queryBuilder'; @@ -15,22 +12,17 @@ import TimeSeriesView from './TimeSeriesView'; function TimeSeriesViewContainer({ dataSource = DataSource.TRACES, }: TimeSeriesViewProps): JSX.Element { - const { stagedQuery } = useQueryBuilder(); + const { stagedQuery, panelType } = useQueryBuilder(); const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector< AppState, GlobalReducer >((state) => state.globalTime); - const { queryData: panelTypeParam } = useUrlQueryData( - PANEL_TYPES_QUERY, - PANEL_TYPES.TIME_SERIES, - ); - const { data, isLoading, isError } = useGetQueryRange( { query: stagedQuery || initialQueriesMap[dataSource], - graphType: panelTypeParam, + graphType: panelType || PANEL_TYPES.TIME_SERIES, selectedTime: 'GLOBAL_TIME', globalSelectedInterval: globalSelectedTime, params: { @@ -45,7 +37,7 @@ function TimeSeriesViewContainer({ minTime, stagedQuery, ], - enabled: !!stagedQuery && panelTypeParam === PANEL_TYPES.TIME_SERIES, + enabled: !!stagedQuery && panelType === PANEL_TYPES.TIME_SERIES, }, ); diff --git a/frontend/src/container/TracesExplorer/ListView/index.tsx b/frontend/src/container/TracesExplorer/ListView/index.tsx index 674184f749..e93b11b1f4 100644 --- a/frontend/src/container/TracesExplorer/ListView/index.tsx +++ b/frontend/src/container/TracesExplorer/ListView/index.tsx @@ -65,7 +65,8 @@ function ListView(): JSX.Element { paginationQueryData, options?.selectColumns, ], - enabled: !!stagedQuery && panelType === PANEL_TYPES.LIST, + enabled: + !!stagedQuery && panelType === PANEL_TYPES.LIST && !!options?.selectColumns, }, ); diff --git a/frontend/src/container/TracesExplorer/ListView/styles.ts b/frontend/src/container/TracesExplorer/ListView/styles.ts index 834dd5209e..292b04b1f9 100644 --- a/frontend/src/container/TracesExplorer/ListView/styles.ts +++ b/frontend/src/container/TracesExplorer/ListView/styles.ts @@ -15,3 +15,7 @@ export const Container = styled.div` export const ErrorText = styled(Typography)` text-align: center; `; + +export const DateText = styled(Typography)` + min-width: 145px; +`; diff --git a/frontend/src/container/TracesExplorer/ListView/utils.tsx b/frontend/src/container/TracesExplorer/ListView/utils.tsx index 59f36a88ab..eb6be9a90d 100644 --- a/frontend/src/container/TracesExplorer/ListView/utils.tsx +++ b/frontend/src/container/TracesExplorer/ListView/utils.tsx @@ -9,6 +9,8 @@ import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { QueryDataV3 } from 'types/api/widgets/getQuery'; +import { DateText } from './styles'; + export const transformDataWithDate = (data: QueryDataV3[]): QueryDataV3[] => data.map((query) => ({ ...query, @@ -78,9 +80,10 @@ export const modifyColumns = ( if (key === 'date') { return { ...column, + width: 145, render: (date: string): JSX.Element => { const day = dayjs(date); - return {day.format('YYYY/MM/DD HH:mm:ss')}; + return {day.format('YYYY/MM/DD HH:mm:ss')}; }, }; } diff --git a/frontend/src/hooks/queryBuilder/useQueryOperations.ts b/frontend/src/hooks/queryBuilder/useQueryOperations.ts index 540614d838..02af0118d0 100644 --- a/frontend/src/hooks/queryBuilder/useQueryOperations.ts +++ b/frontend/src/hooks/queryBuilder/useQueryOperations.ts @@ -57,9 +57,16 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => { ); const getNewListOfAdditionalFilters = useCallback( - (dataSource: DataSource): string[] => - mapOfFilters[dataSource].map((item) => item.text), - [], + (dataSource: DataSource): string[] => { + const listOfFilters = mapOfFilters[dataSource].map((item) => item.text); + + if (panelType === PANEL_TYPES.LIST) { + return listOfFilters.filter((filter) => filter !== 'Aggregation interval'); + } + + return listOfFilters; + }, + [panelType], ); const handleChangeAggregatorAttribute = useCallback( diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index c70f318dc0..179a9a93e0 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -52,15 +52,26 @@ function TracesExplorer(): JSX.Element { return groupByCount > 0; }, [currentQuery]); - const defaultQuery = useMemo( - () => - updateAllQueriesOperators( - initialQueriesMap.traces, - PANEL_TYPES.LIST, - DataSource.TRACES, - ), - [updateAllQueriesOperators], - ); + const defaultQuery = useMemo(() => { + const query = updateAllQueriesOperators( + initialQueriesMap.traces, + PANEL_TYPES.LIST, + DataSource.TRACES, + ); + + return { + ...query, + builder: { + ...query.builder, + queryData: [ + { + ...query.builder.queryData[0], + orderBy: [{ columnName: 'timestamp', order: 'desc' }], + }, + ], + }, + }; + }, [updateAllQueriesOperators]); const tabsItems = getTabsItems({ isListViewDisabled: isMultipleQueries || isGroupByExist, From dba4e00b02d03c6105797c9d5ce30685adb09e70 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Wed, 5 Jul 2023 23:58:43 +0530 Subject: [PATCH 28/29] =?UTF-8?q?chore(release):=20=F0=9F=93=8C=20pin=20ve?= =?UTF-8?q?rsions:=20SigNoz=200.22.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/docker-swarm/clickhouse-setup/docker-compose.yaml | 4 ++-- deploy/docker/clickhouse-setup/docker-compose.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index bd34be3364..d5b7813ca7 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -137,7 +137,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.21.0 + image: signoz/query-service:0.22.0 command: ["-config=/root/config/prometheus.yml"] # ports: # - "6060:6060" # pprof port @@ -166,7 +166,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:0.21.0 + image: signoz/frontend:0.22.0 deploy: restart_policy: condition: on-failure diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 14709f7c34..73608537cc 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -153,7 +153,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.21.0} + image: signoz/query-service:${DOCKER_TAG:-0.22.0} container_name: query-service command: ["-config=/root/config/prometheus.yml"] # ports: @@ -181,7 +181,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.21.0} + image: signoz/frontend:${DOCKER_TAG:-0.22.0} container_name: frontend restart: on-failure depends_on: From 720edb162e39cb97c87aca9b3f19ecf549941f8d Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Thu, 6 Jul 2023 00:54:26 +0530 Subject: [PATCH 29/29] =?UTF-8?q?chore(release):=20=F0=9F=93=8C=20pin=20ve?= =?UTF-8?q?rsions:=20SigNoz=20OtelCollector=200.79.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/docker-swarm/clickhouse-setup/docker-compose.yaml | 4 ++-- deploy/docker/clickhouse-setup/docker-compose-core.yaml | 4 ++-- deploy/docker/clickhouse-setup/docker-compose.yaml | 4 ++-- pkg/query-service/tests/test-deploy/docker-compose.yaml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index d5b7813ca7..34c639ae43 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -179,7 +179,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:0.79.1 + image: signoz/signoz-otel-collector:0.79.2 command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] user: root # required for reading docker container logs volumes: @@ -208,7 +208,7 @@ services: <<: *clickhouse-depend otel-collector-metrics: - image: signoz/signoz-otel-collector:0.79.1 + image: signoz/signoz-otel-collector:0.79.2 command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml index 5e2db19d89..79ae0c765e 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml @@ -41,7 +41,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` otel-collector: container_name: otel-collector - image: signoz/signoz-otel-collector:0.79.1 + image: signoz/signoz-otel-collector:0.79.2 command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] # user: root # required for reading docker container logs volumes: @@ -67,7 +67,7 @@ services: otel-collector-metrics: container_name: otel-collector-metrics - image: signoz/signoz-otel-collector:0.79.1 + image: signoz/signoz-otel-collector:0.79.2 command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 73608537cc..5d05f15528 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -193,7 +193,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.1} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.2} command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] user: root # required for reading docker container logs volumes: @@ -219,7 +219,7 @@ services: <<: *clickhouse-depend otel-collector-metrics: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.1} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.2} command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/pkg/query-service/tests/test-deploy/docker-compose.yaml b/pkg/query-service/tests/test-deploy/docker-compose.yaml index faaae9a717..a511230df6 100644 --- a/pkg/query-service/tests/test-deploy/docker-compose.yaml +++ b/pkg/query-service/tests/test-deploy/docker-compose.yaml @@ -169,7 +169,7 @@ services: <<: *clickhouse-depends otel-collector: - image: signoz/signoz-otel-collector:0.79.1 + image: signoz/signoz-otel-collector:0.79.2 command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] user: root # required for reading docker container logs volumes: @@ -195,7 +195,7 @@ services: <<: *clickhouse-depends otel-collector-metrics: - image: signoz/signoz-otel-collector:0.79.1 + image: signoz/signoz-otel-collector:0.79.2 command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml