diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index c25296262a..aa5cbb5ff3 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.23.1 + image: signoz/query-service:0.24.0 command: ["-config=/root/config/prometheus.yml"] # ports: # - "6060:6060" # pprof port @@ -166,7 +166,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:0.23.1 + image: signoz/frontend:0.24.0 deploy: restart_policy: condition: on-failure @@ -179,7 +179,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:0.79.3 + image: signoz/signoz-otel-collector:0.79.4 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.3 + image: signoz/signoz-otel-collector:0.79.4 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 4e59682c4b..5b6caa920f 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.3 + image: signoz/signoz-otel-collector:0.79.4 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.3 + image: signoz/signoz-otel-collector:0.79.4 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 c3d33f19c9..5f16d26489 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.23.1} + image: signoz/query-service:${DOCKER_TAG:-0.24.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.23.1} + image: signoz/frontend:${DOCKER_TAG:-0.24.0} container_name: frontend restart: on-failure depends_on: @@ -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.3} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.4} 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.3} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.4} 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/frontend/package.json b/frontend/package.json index c691a1c44c..c6208a9a82 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -69,6 +69,7 @@ "papaparse": "5.4.1", "react": "18.2.0", "react-dom": "18.2.0", + "react-drag-listview": "2.0.0", "react-force-graph": "^1.41.0", "react-grid-layout": "^1.3.4", "react-i18next": "^11.16.1", diff --git a/frontend/src/api/user/loginPrecheck.ts b/frontend/src/api/user/loginPrecheck.ts index 934c101f0e..c0cdc3dcc4 100644 --- a/frontend/src/api/user/loginPrecheck.ts +++ b/frontend/src/api/user/loginPrecheck.ts @@ -9,9 +9,9 @@ const loginPrecheck = async ( ): Promise | ErrorResponse> => { try { const response = await axios.get( - `/loginPrecheck?email=${props.email}&ref=${encodeURIComponent( - window.location.href, - )}`, + `/loginPrecheck?email=${encodeURIComponent( + props.email, + )}&ref=${encodeURIComponent(window.location.href)}`, ); return { diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index 6f0f475bac..cd988286cd 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -288,12 +288,11 @@ function Graph({ if (chartHasData) { chartPlugins.push(createIntersectionCursorPlugin()); chartPlugins.push(createDragSelectPlugin()); + chartPlugins.push(legend(name, data.datasets.length > 3)); } else { chartPlugins.push(emptyGraph); } - chartPlugins.push(legend(name, data.datasets.length > 3)); - lineChartRef.current = new Chart(chartRef.current, { type, data, diff --git a/frontend/src/components/ResizeTable/ResizeTable.tsx b/frontend/src/components/ResizeTable/ResizeTable.tsx index d6898d0815..90cc588c47 100644 --- a/frontend/src/components/ResizeTable/ResizeTable.tsx +++ b/frontend/src/components/ResizeTable/ResizeTable.tsx @@ -1,6 +1,8 @@ +/* eslint-disable react/jsx-props-no-spreading */ + import { Table } from 'antd'; -import type { TableProps } from 'antd/es/table'; import { ColumnsType } from 'antd/lib/table'; +import { dragColumnParams } from 'hooks/useDragColumns/configs'; import { SyntheticEvent, useCallback, @@ -8,12 +10,18 @@ import { useMemo, useState, } from 'react'; +import ReactDragListView from 'react-drag-listview'; import { ResizeCallbackData } from 'react-resizable'; import ResizableHeader from './ResizableHeader'; +import { DragSpanStyle } from './styles'; +import { ResizeTableProps } from './types'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function ResizeTable({ columns, ...restprops }: TableProps): JSX.Element { +function ResizeTable({ + columns, + onDragColumn, + ...restProps +}: ResizeTableProps): JSX.Element { const [columnsData, setColumns] = useState([]); const handleResize = useCallback( @@ -31,16 +39,32 @@ function ResizeTable({ columns, ...restprops }: TableProps): JSX.Element { [columnsData], ); - const mergeColumns = useMemo( + const mergedColumns = useMemo( () => columnsData.map((col, index) => ({ ...col, + ...(onDragColumn && { + title: ( + + {col?.title?.toString() || ''} + + ), + }), onHeaderCell: (column: ColumnsType[number]): unknown => ({ width: column.width, onResize: handleResize(index), }), - })), - [columnsData, handleResize], + })) as ColumnsType, + [columnsData, onDragColumn, handleResize], + ); + + const tableParams = useMemo( + () => ({ + ...restProps, + components: { header: { cell: ResizableHeader } }, + columns: mergedColumns, + }), + [mergedColumns, restProps], ); useEffect(() => { @@ -49,15 +73,17 @@ function ResizeTable({ columns, ...restprops }: TableProps): JSX.Element { } }, [columns]); - return ( - } - /> + return onDragColumn ? ( + +
+ + ) : ( +
); } +ResizeTable.defaultProps = { + onDragColumn: undefined, +}; + export default ResizeTable; diff --git a/frontend/src/components/ResizeTable/styles.ts b/frontend/src/components/ResizeTable/styles.ts index acb0c28219..e69b208348 100644 --- a/frontend/src/components/ResizeTable/styles.ts +++ b/frontend/src/components/ResizeTable/styles.ts @@ -2,10 +2,16 @@ import styled from 'styled-components'; export const SpanStyle = styled.span` position: absolute; - right: -5px; + right: -0.313rem; bottom: 0; z-index: 1; - width: 10px; + width: 0.625rem; height: 100%; cursor: col-resize; `; + +export const DragSpanStyle = styled.span` + display: flex; + margin: -1rem; + padding: 1rem; +`; diff --git a/frontend/src/components/ResizeTable/types.ts b/frontend/src/components/ResizeTable/types.ts new file mode 100644 index 0000000000..6390a25ba6 --- /dev/null +++ b/frontend/src/components/ResizeTable/types.ts @@ -0,0 +1,6 @@ +import { TableProps } from 'antd'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface ResizeTableProps extends TableProps { + onDragColumn?: (fromIndex: number, toIndex: number) => void; +} diff --git a/frontend/src/constants/features.ts b/frontend/src/constants/features.ts index cceaf2817b..c5f4a1e7b8 100644 --- a/frontend/src/constants/features.ts +++ b/frontend/src/constants/features.ts @@ -8,4 +8,5 @@ export enum FeatureKeys { QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS', QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS', DISABLE_UPSELL = 'DISABLE_UPSELL', + USE_SPAN_METRICS = 'USE_SPAN_METRICS', } diff --git a/frontend/src/constants/localStorage.ts b/frontend/src/constants/localStorage.ts index 6460e094c0..63aee6b30a 100644 --- a/frontend/src/constants/localStorage.ts +++ b/frontend/src/constants/localStorage.ts @@ -8,4 +8,6 @@ export enum LOCALSTORAGE { LOGS_LINES_PER_ROW = 'LOGS_LINES_PER_ROW', LOGS_LIST_OPTIONS = 'LOGS_LIST_OPTIONS', TRACES_LIST_OPTIONS = 'TRACES_LIST_OPTIONS', + TRACES_LIST_COLUMNS = 'TRACES_LIST_COLUMNS', + LOGS_LIST_COLUMNS = 'LOGS_LIST_COLUMNS', } diff --git a/frontend/src/container/ExplorerOrderBy/index.tsx b/frontend/src/container/ExplorerOrderBy/index.tsx new file mode 100644 index 0000000000..5374510084 --- /dev/null +++ b/frontend/src/container/ExplorerOrderBy/index.tsx @@ -0,0 +1,72 @@ +import { Select, Spin } from 'antd'; +import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces'; +import { useOrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter/useOrderByFilter'; +import { selectStyle } from 'container/QueryBuilder/filters/QueryBuilderSearch/config'; +import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; +import { memo, useMemo } from 'react'; +import { StringOperators } from 'types/common/queryBuilder'; + +function ExplorerOrderBy({ query, onChange }: OrderByFilterProps): JSX.Element { + const { + debouncedSearchText, + selectedValue, + aggregationOptions, + generateOptions, + createOptions, + handleChange, + handleSearchKeys, + } = useOrderByFilter({ query, onChange }); + + const { data, isFetching } = useGetAggregateKeys( + { + aggregateAttribute: query.aggregateAttribute.key, + dataSource: query.dataSource, + aggregateOperator: query.aggregateOperator, + searchText: debouncedSearchText, + }, + { + keepPreviousData: true, + }, + ); + + const options = useMemo(() => { + const keysOptions = createOptions(data?.payload?.attributeKeys || []); + + const customOptions = createOptions([ + { key: 'timestamp', isColumn: true, type: null, dataType: null }, + ]); + + const baseOptions = [ + ...customOptions, + ...(query.aggregateOperator === StringOperators.NOOP + ? [] + : aggregationOptions), + ...keysOptions, + ]; + + return generateOptions(baseOptions); + }, [ + aggregationOptions, + createOptions, + data?.payload?.attributeKeys, + generateOptions, + query.aggregateOperator, + ]); + + return ( + ; const element: ColumnTypeRender> = column.render( @@ -60,20 +78,29 @@ function InfinityTable({ })} ), - [columns], + [tableColumns], ); const tableHeader = useCallback( () => ( - {columns.map((column) => ( - - {column.title as string} - - ))} + {tableColumns.map((column) => { + const isDragColumn = column.key !== 'expand'; + + return ( + + {column.title as string} + + ); + })} ), - [columns], + [tableColumns], ); return ( @@ -81,7 +108,8 @@ function InfinityTable({ style={infinityDefaultStyles} data={dataSource} components={{ - Table: CustomTable, + // eslint-disable-next-line react/jsx-props-no-spreading + Table: LogsCustomTable({ handleDragEnd }), // TODO: fix it in the future // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts b/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts index a45d2d6b02..024ba88a9e 100644 --- a/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts +++ b/frontend/src/container/LogsExplorerList/InfinityTableView/styles.ts @@ -1,6 +1,10 @@ import { themeColors } from 'constants/theme'; import styled from 'styled-components'; +interface TableHeaderCellStyledProps { + isDragColumn: boolean; +} + export const TableStyled = styled.table` width: 100%; border-top: 1px solid rgba(253, 253, 253, 0.12); @@ -26,10 +30,12 @@ export const TableRowStyled = styled.tr` } `; -export const TableHeaderCellStyled = styled.th` +export const TableHeaderCellStyled = styled.th` padding: 0.5rem; border-inline-end: 1px solid rgba(253, 253, 253, 0.12); background-color: #1d1d1d; + ${({ isDragColumn }): string => (isDragColumn ? 'cursor: col-resize;' : '')} + &:first-child { border-start-start-radius: 2px; } diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index 4536b1be49..a2fadefc53 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -3,6 +3,7 @@ import LogDetail from 'components/LogDetail'; import TabLabel from 'components/TabLabel'; import { QueryParams } from 'constants/query'; import { + initialAutocompleteData, initialQueriesMap, OPERATORS, PANEL_TYPES, @@ -17,6 +18,7 @@ import LogsExplorerChart from 'container/LogsExplorerChart'; import LogsExplorerList from 'container/LogsExplorerList'; import LogsExplorerTable from 'container/LogsExplorerTable'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { SIGNOZ_VALUE } from 'container/QueryBuilder/filters/OrderByFilter/constants'; import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils'; @@ -73,6 +75,7 @@ function LogsExplorerViews(): JSX.Element { stagedQuery, panelType, updateAllQueriesOperators, + updateQueriesData, redirectWithQueryBuilderData, } = useQueryBuilder(); @@ -175,26 +178,40 @@ function LogsExplorerViews(): JSX.Element { setActiveLog(null); }, []); + const getUpdateQuery = useCallback( + (newPanelType: GRAPH_TYPES): Query => { + let query = updateAllQueriesOperators( + currentQuery, + newPanelType, + DataSource.TRACES, + ); + + if (newPanelType === PANEL_TYPES.LIST) { + query = updateQueriesData(query, 'queryData', (item) => ({ + ...item, + orderBy: item.orderBy.filter((item) => item.columnName !== SIGNOZ_VALUE), + aggregateAttribute: initialAutocompleteData, + })); + } + + return query; + }, + [currentQuery, updateAllQueriesOperators, updateQueriesData], + ); + const handleChangeView = useCallback( - (newPanelType: string) => { + (type: string) => { + const newPanelType = type as GRAPH_TYPES; + if (newPanelType === panelType) return; - const query = updateAllQueriesOperators( - currentQuery, - newPanelType as GRAPH_TYPES, - DataSource.LOGS, - ); + const query = getUpdateQuery(newPanelType); redirectWithQueryBuilderData(query, { [queryParamNamesMap.panelTypes]: newPanelType, }); }, - [ - currentQuery, - panelType, - updateAllQueriesOperators, - redirectWithQueryBuilderData, - ], + [panelType, getUpdateQuery, redirectWithQueryBuilderData], ); const getRequestData = useCallback( diff --git a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts index 366dc864a6..880d9edba9 100644 --- a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts +++ b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts @@ -2,7 +2,10 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; import { Widgets } from 'types/api/dashboard/getAll'; import { v4 } from 'uuid'; -export const getWidgetQueryBuilder = (query: Widgets['query']): Widgets => ({ +export const getWidgetQueryBuilder = ( + query: Widgets['query'], + title = '', +): Widgets => ({ description: '', id: v4(), isStacked: false, @@ -11,5 +14,5 @@ export const getWidgetQueryBuilder = (query: Widgets['query']): Widgets => ({ panelTypes: PANEL_TYPES.TIME_SERIES, query, timePreferance: 'GLOBAL_TIME', - title: '', + title, }); diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts index e85b4d4ace..c1896b6884 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts @@ -1,7 +1,10 @@ +import { OPERATORS } from 'constants/queryBuilder'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; -import { QueryBuilderData } from 'types/common/queryBuilder'; +import { DataSource, QueryBuilderData } from 'types/common/queryBuilder'; +import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant'; +import { IServiceName } from '../Tabs/types'; import { getQueryBuilderQueries, getQueryBuilderQuerieswithFormula, @@ -12,35 +15,42 @@ export const databaseCallsRPS = ({ legend, tagFilterItems, }: DatabaseCallsRPSProps): QueryBuilderData => { - const metricName: BaseAutocompleteData = { - dataType: 'float64', - isColumn: true, - key: 'signoz_db_latency_count', - type: null, - }; - const groupBy: BaseAutocompleteData[] = [ - { dataType: 'string', isColumn: false, key: 'db_system', type: 'tag' }, - ]; - const itemsA: TagFilterItem[] = [ + const autocompleteData: BaseAutocompleteData[] = [ { - id: '', - key: { - dataType: 'string', - isColumn: false, - key: 'service_name', - type: 'resource', - }, - op: 'IN', - value: [`${servicename}`], + key: WidgetKeys.SignozDBLatencyCount, + dataType: DataType.FLOAT64, + isColumn: true, + type: null, }, - ...tagFilterItems, + ]; + const groupBy: BaseAutocompleteData[] = [ + { dataType: DataType.STRING, isColumn: false, key: 'db_system', type: 'tag' }, + ]; + const filterItems: TagFilterItem[][] = [ + [ + { + id: '', + key: { + key: WidgetKeys.Service_name, + dataType: DataType.STRING, + isColumn: false, + type: MetricsType.Resource, + }, + op: OPERATORS.IN, + value: [`${servicename}`], + }, + ...tagFilterItems, + ], ]; + const legends = [legend]; + return getQueryBuilderQueries({ - metricName, + autocompleteData, groupBy, - legend, - itemsA, + legends, + filterItems, + dataSource: DataSource.METRICS, }); }; @@ -48,32 +58,29 @@ export const databaseCallsAvgDuration = ({ servicename, tagFilterItems, }: DatabaseCallProps): QueryBuilderData => { - const metricNameA: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataA: BaseAutocompleteData = { + key: WidgetKeys.SignozDbLatencySum, + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_db_latency_sum', type: null, }; - const metricNameB: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataB: BaseAutocompleteData = { + key: WidgetKeys.SignozDBLatencyCount, + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_db_latency_count', type: null, }; - const expression = 'A/B'; - const legendFormula = 'Average Duration'; - const legend = ''; - const disabled = true; + const additionalItemsA: TagFilterItem[] = [ { id: '', key: { - dataType: 'string', + key: WidgetKeys.Service_name, + dataType: DataType.STRING, isColumn: false, - key: 'service_name', - type: 'resource', + type: MetricsType.Resource, }, - op: 'IN', + op: OPERATORS.IN, value: [`${servicename}`], }, ...tagFilterItems, @@ -81,14 +88,14 @@ export const databaseCallsAvgDuration = ({ const additionalItemsB = additionalItemsA; return getQueryBuilderQuerieswithFormula({ - metricNameA, - metricNameB, + autocompleteDataA, + autocompleteDataB, additionalItemsA, additionalItemsB, - legend, - disabled, - expression, - legendFormula, + legend: '', + disabled: true, + expression: FORMULA.DATABASE_CALLS_AVG_DURATION, + legendFormula: 'Average Duration', }); }; @@ -97,6 +104,6 @@ interface DatabaseCallsRPSProps extends DatabaseCallProps { } interface DatabaseCallProps { - servicename: string | undefined; + servicename: IServiceName['servicename']; tagFilterItems: TagFilterItem[]; } diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts index 69af5042f2..1d375f2e53 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts @@ -1,14 +1,22 @@ +import { OPERATORS } from 'constants/queryBuilder'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; -import { QueryBuilderData } from 'types/common/queryBuilder'; +import { DataSource, QueryBuilderData } from 'types/common/queryBuilder'; +import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant'; +import { IServiceName } from '../Tabs/types'; import { getQueryBuilderQueries, getQueryBuilderQuerieswithFormula, } from './MetricsPageQueriesFactory'; const groupBy: BaseAutocompleteData[] = [ - { dataType: 'string', isColumn: false, key: 'address', type: 'tag' }, + { + dataType: DataType.STRING, + isColumn: false, + key: WidgetKeys.Address, + type: MetricsType.Tag, + }, ]; export const externalCallErrorPercent = ({ @@ -16,39 +24,39 @@ export const externalCallErrorPercent = ({ legend, tagFilterItems, }: ExternalCallDurationByAddressProps): QueryBuilderData => { - const metricNameA: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataA: BaseAutocompleteData = { + key: WidgetKeys.SignozExternalCallLatencyCount, + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_external_call_latency_count', type: null, }; - const metricNameB: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataB: BaseAutocompleteData = { + key: WidgetKeys.SignozExternalCallLatencyCount, + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_external_call_latency_count', type: null, }; const additionalItemsA: TagFilterItem[] = [ { id: '', key: { - dataType: 'string', + key: WidgetKeys.Service_name, + dataType: DataType.STRING, isColumn: false, - key: 'service_name', - type: 'resource', + type: MetricsType.Resource, }, - op: 'IN', + op: OPERATORS.IN, value: [`${servicename}`], }, { id: '', key: { - dataType: 'int64', + key: WidgetKeys.StatusCode, + dataType: DataType.INT64, isColumn: false, - key: 'status_code', - type: 'tag', + type: MetricsType.Tag, }, - op: 'IN', + op: OPERATORS.IN, value: ['STATUS_CODE_ERROR'], }, ...tagFilterItems, @@ -57,22 +65,22 @@ export const externalCallErrorPercent = ({ { id: '', key: { - dataType: 'string', + key: WidgetKeys.Service_name, + dataType: DataType.STRING, isColumn: false, - key: 'service_name', - type: 'resource', + type: MetricsType.Resource, }, - op: 'IN', + op: OPERATORS.IN, value: [`${servicename}`], }, ...tagFilterItems, ]; const legendFormula = legend; - const expression = 'A*100/B'; + const expression = FORMULA.ERROR_PERCENTAGE; const disabled = true; return getQueryBuilderQuerieswithFormula({ - metricNameA, - metricNameB, + autocompleteDataA, + autocompleteDataB, additionalItemsA, additionalItemsB, legend, @@ -87,19 +95,19 @@ export const externalCallDuration = ({ servicename, tagFilterItems, }: ExternalCallProps): QueryBuilderData => { - const metricNameA: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataA: BaseAutocompleteData = { + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_external_call_latency_sum', + key: WidgetKeys.SignozExternalCallLatencySum, type: null, }; - const metricNameB: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataB: BaseAutocompleteData = { + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_external_call_latency_count', + key: WidgetKeys.SignozExternalCallLatencyCount, type: null, }; - const expression = 'A/B'; + const expression = FORMULA.DATABASE_CALLS_AVG_DURATION; const legendFormula = 'Average Duration'; const legend = ''; const disabled = true; @@ -107,12 +115,12 @@ export const externalCallDuration = ({ { id: '', key: { - dataType: 'string', + dataType: DataType.STRING, isColumn: false, - key: 'service_name', - type: 'resource', + key: WidgetKeys.Service_name, + type: MetricsType.Resource, }, - op: 'IN', + op: OPERATORS.IN, value: [`${servicename}`], }, ...tagFilterItems, @@ -120,8 +128,8 @@ export const externalCallDuration = ({ const additionalItemsB = additionalItemsA; return getQueryBuilderQuerieswithFormula({ - metricNameA, - metricNameB, + autocompleteDataA, + autocompleteDataB, additionalItemsA, additionalItemsB, legend, @@ -136,31 +144,38 @@ export const externalCallRpsByAddress = ({ legend, tagFilterItems, }: ExternalCallDurationByAddressProps): QueryBuilderData => { - const metricName: BaseAutocompleteData = { - dataType: 'float64', - isColumn: true, - key: 'signoz_external_call_latency_count', - type: null, - }; - const itemsA: TagFilterItem[] = [ + const autocompleteData: BaseAutocompleteData[] = [ { - id: '', - key: { - dataType: 'string', - isColumn: false, - key: 'service_name', - type: 'resource', - }, - op: 'IN', - value: [`${servicename}`], + dataType: DataType.FLOAT64, + isColumn: true, + key: WidgetKeys.SignozExternalCallLatencyCount, + type: null, }, - ...tagFilterItems, ]; + const filterItems: TagFilterItem[][] = [ + [ + { + id: '', + key: { + dataType: DataType.STRING, + isColumn: false, + key: WidgetKeys.Service_name, + type: MetricsType.Resource, + }, + op: OPERATORS.IN, + value: [`${servicename}`], + }, + ...tagFilterItems, + ], + ]; + + const legends: string[] = [legend]; return getQueryBuilderQueries({ - metricName, + autocompleteData, groupBy, - legend, - itemsA, + legends, + filterItems, + dataSource: DataSource.METRICS, }); }; @@ -169,31 +184,31 @@ export const externalCallDurationByAddress = ({ legend, tagFilterItems, }: ExternalCallDurationByAddressProps): QueryBuilderData => { - const metricNameA: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataA: BaseAutocompleteData = { + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_external_call_latency_sum', + key: WidgetKeys.SignozExternalCallLatencySum, type: null, }; - const metricNameB: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataB: BaseAutocompleteData = { + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_external_call_latency_count', + key: WidgetKeys.SignozExternalCallLatencyCount, type: null, }; - const expression = 'A/B'; + const expression = FORMULA.DATABASE_CALLS_AVG_DURATION; const legendFormula = legend; const disabled = true; const additionalItemsA: TagFilterItem[] = [ { id: '', key: { - dataType: 'string', + dataType: DataType.STRING, isColumn: false, - key: 'service_name', - type: 'resource', + key: WidgetKeys.Service_name, + type: MetricsType.Resource, }, - op: 'IN', + op: OPERATORS.IN, value: [`${servicename}`], }, ...tagFilterItems, @@ -201,8 +216,8 @@ export const externalCallDurationByAddress = ({ const additionalItemsB = additionalItemsA; return getQueryBuilderQuerieswithFormula({ - metricNameA, - metricNameB, + autocompleteDataA, + autocompleteDataB, additionalItemsA, additionalItemsB, legend, @@ -218,6 +233,6 @@ interface ExternalCallDurationByAddressProps extends ExternalCallProps { } export interface ExternalCallProps { - servicename: string | undefined; + servicename: IServiceName['servicename']; tagFilterItems: TagFilterItem[]; } diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts index b5d3cb2bd6..58f535cd04 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts @@ -5,44 +5,64 @@ import { import getStep from 'lib/getStep'; import store from 'store'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { + IBuilderQuery, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; +import { + DataSource, MetricAggregateOperator, QueryBuilderData, } from 'types/common/queryBuilder'; export const getQueryBuilderQueries = ({ - metricName, + autocompleteData, groupBy = [], - legend, - itemsA, + legends, + filterItems, + aggregateOperator, + dataSource, + queryNameAndExpression, }: BuilderQueriesProps): QueryBuilderData => ({ queryFormulas: [], - queryData: [ - { + queryData: autocompleteData.map((item, index) => { + const newQueryData: IBuilderQuery = { ...initialQueryBuilderFormValuesMap.metrics, - aggregateOperator: MetricAggregateOperator.SUM_RATE, + aggregateOperator: ((): string => { + if (aggregateOperator) { + return aggregateOperator[index]; + } + return MetricAggregateOperator.SUM_RATE; + })(), disabled: false, groupBy, - aggregateAttribute: metricName, - legend, + aggregateAttribute: item, + legend: legends[index], stepInterval: getStep({ end: store.getState().globalTime.maxTime, inputFormat: 'ns', start: store.getState().globalTime.minTime, }), - reduceTo: 'sum', filters: { - items: itemsA, + items: filterItems[index], op: 'AND', }, - }, - ], + reduceTo: 'sum', + dataSource, + }; + + if (queryNameAndExpression) { + newQueryData.queryName = queryNameAndExpression[index]; + newQueryData.expression = queryNameAndExpression[index]; + } + + return newQueryData; + }), }); export const getQueryBuilderQuerieswithFormula = ({ - metricNameA, - metricNameB, + autocompleteDataA, + autocompleteDataB, additionalItemsA, additionalItemsB, legend, @@ -65,7 +85,7 @@ export const getQueryBuilderQuerieswithFormula = ({ disabled, groupBy, legend, - aggregateAttribute: metricNameA, + aggregateAttribute: autocompleteDataA, reduceTo: 'sum', filters: { items: additionalItemsA, @@ -83,7 +103,7 @@ export const getQueryBuilderQuerieswithFormula = ({ disabled, groupBy, legend, - aggregateAttribute: metricNameB, + aggregateAttribute: autocompleteDataB, queryName: 'B', expression: 'B', reduceTo: 'sum', @@ -101,15 +121,18 @@ export const getQueryBuilderQuerieswithFormula = ({ }); interface BuilderQueriesProps { - metricName: BaseAutocompleteData; + autocompleteData: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[]; - legend: string; - itemsA: TagFilterItem[]; + legends: string[]; + filterItems: TagFilterItem[][]; + aggregateOperator?: string[]; + dataSource: DataSource; + queryNameAndExpression?: string[]; } interface BuilderQuerieswithFormulaProps { - metricNameA: BaseAutocompleteData; - metricNameB: BaseAutocompleteData; + autocompleteDataA: BaseAutocompleteData; + autocompleteDataB: BaseAutocompleteData; legend: string; disabled: boolean; groupBy?: BaseAutocompleteData[]; diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts index 97a72c4fd2..ec2d7b9272 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts @@ -1,55 +1,131 @@ +import { OPERATORS } from 'constants/queryBuilder'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; -import { QueryBuilderData } from 'types/common/queryBuilder'; +import { DataSource, QueryBuilderData } from 'types/common/queryBuilder'; +import { + DataType, + FORMULA, + GraphTitle, + LATENCY_AGGREGATEOPERATOR, + LATENCY_AGGREGATEOPERATOR_SPAN_METRICS, + MetricsType, + OPERATION_LEGENDS, + QUERYNAME_AND_EXPRESSION, + WidgetKeys, +} from '../constant'; +import { IServiceName } from '../Tabs/types'; import { getQueryBuilderQueries, getQueryBuilderQuerieswithFormula, } from './MetricsPageQueriesFactory'; +export const latency = ({ + servicename, + tagFilterItems, + isSpanMetricEnable = false, + topLevelOperationsRoute, +}: LatencyProps): QueryBuilderData => { + const newAutoCompleteData: BaseAutocompleteData = { + key: isSpanMetricEnable + ? WidgetKeys.Signoz_latency_bucket + : WidgetKeys.DurationNano, + dataType: DataType.FLOAT64, + isColumn: true, + type: isSpanMetricEnable ? null : MetricsType.Tag, + }; + + const autocompleteData: BaseAutocompleteData[] = Array(3).fill( + newAutoCompleteData, + ); + + const filterItem: TagFilterItem[] = [ + { + id: '', + key: { + key: isSpanMetricEnable ? WidgetKeys.Service_name : WidgetKeys.ServiceName, + dataType: DataType.STRING, + type: isSpanMetricEnable ? MetricsType.Resource : MetricsType.Tag, + isColumn: !isSpanMetricEnable, + }, + op: isSpanMetricEnable ? OPERATORS.IN : OPERATORS['='], + value: isSpanMetricEnable ? [servicename] : servicename, + }, + { + id: '', + key: { + dataType: DataType.STRING, + isColumn: !isSpanMetricEnable, + key: isSpanMetricEnable ? WidgetKeys.Operation : WidgetKeys.Name, + type: MetricsType.Tag, + }, + op: OPERATORS.IN.toLowerCase(), // TODO: need to remove toLowerCase() this once backend is changed + value: [...topLevelOperationsRoute], + }, + ...tagFilterItems, + ]; + + const filterItems: TagFilterItem[][] = Array(3).fill([...filterItem]); + + return getQueryBuilderQueries({ + autocompleteData, + legends: LATENCY_AGGREGATEOPERATOR, + filterItems, + aggregateOperator: isSpanMetricEnable + ? LATENCY_AGGREGATEOPERATOR_SPAN_METRICS + : LATENCY_AGGREGATEOPERATOR, + dataSource: isSpanMetricEnable ? DataSource.METRICS : DataSource.TRACES, + queryNameAndExpression: QUERYNAME_AND_EXPRESSION, + }); +}; + export const operationPerSec = ({ servicename, tagFilterItems, topLevelOperations, }: OperationPerSecProps): QueryBuilderData => { - const metricName: BaseAutocompleteData = { - dataType: 'float64', - isColumn: true, - key: 'signoz_latency_count', - type: null, - }; - const legend = 'Operations'; + const autocompleteData: BaseAutocompleteData[] = [ + { + key: WidgetKeys.SignozLatencyCount, + dataType: DataType.FLOAT64, + isColumn: true, + type: null, + }, + ]; - const itemsA: TagFilterItem[] = [ - { - id: '', - key: { - dataType: 'string', - isColumn: false, - key: 'service_name', - type: 'resource', + const filterItems: TagFilterItem[][] = [ + [ + { + id: '', + key: { + key: WidgetKeys.Service_name, + dataType: DataType.STRING, + isColumn: false, + type: MetricsType.Resource, + }, + op: OPERATORS.IN, + value: [`${servicename}`], }, - op: 'IN', - value: [`${servicename}`], - }, - { - id: '', - key: { - dataType: 'string', - isColumn: false, - key: 'operation', - type: 'tag', + { + id: '', + key: { + key: WidgetKeys.Operation, + dataType: DataType.STRING, + isColumn: false, + type: MetricsType.Tag, + }, + op: OPERATORS.IN, + value: topLevelOperations, }, - op: 'IN', - value: topLevelOperations, - }, - ...tagFilterItems, + ...tagFilterItems, + ], ]; return getQueryBuilderQueries({ - metricName, - legend, - itemsA, + autocompleteData, + legends: OPERATION_LEGENDS, + filterItems, + dataSource: DataSource.METRICS, }); }; @@ -58,50 +134,50 @@ export const errorPercentage = ({ tagFilterItems, topLevelOperations, }: OperationPerSecProps): QueryBuilderData => { - const metricNameA: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataA: BaseAutocompleteData = { + key: WidgetKeys.SignozCallsTotal, + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_calls_total', type: null, }; - const metricNameB: BaseAutocompleteData = { - dataType: 'float64', + const autocompleteDataB: BaseAutocompleteData = { + key: WidgetKeys.SignozCallsTotal, + dataType: DataType.FLOAT64, isColumn: true, - key: 'signoz_calls_total', type: null, }; const additionalItemsA: TagFilterItem[] = [ { id: '', key: { - dataType: 'string', + key: WidgetKeys.Service_name, + dataType: DataType.STRING, isColumn: false, - key: 'service_name', - type: 'resource', + type: MetricsType.Resource, }, - op: 'IN', + op: OPERATORS.IN, value: [`${servicename}`], }, { id: '', key: { - dataType: 'string', + key: WidgetKeys.Operation, + dataType: DataType.STRING, isColumn: false, - key: 'operation', - type: 'tag', + type: MetricsType.Tag, }, - op: 'IN', + op: OPERATORS.IN, value: topLevelOperations, }, { id: '', key: { - dataType: 'int64', + key: WidgetKeys.StatusCode, + dataType: DataType.INT64, isColumn: false, - key: 'status_code', - type: 'tag', + type: MetricsType.Tag, }, - op: 'IN', + op: OPERATORS.IN, value: ['STATUS_CODE_ERROR'], }, ...tagFilterItems, @@ -111,46 +187,49 @@ export const errorPercentage = ({ { id: '', key: { - dataType: 'string', + key: WidgetKeys.Service_name, + dataType: DataType.STRING, isColumn: false, - key: 'service_name', - type: 'resource', + type: MetricsType.Resource, }, - op: 'IN', + op: OPERATORS.IN, value: [`${servicename}`], }, { id: '', key: { - dataType: 'string', + key: WidgetKeys.Operation, + dataType: DataType.STRING, isColumn: false, - key: 'operation', - type: 'tag', + type: MetricsType.Tag, }, - op: 'IN', + op: OPERATORS.IN, value: topLevelOperations, }, ...tagFilterItems, ]; - const legendFormula = 'Error Percentage'; - const legend = legendFormula; - const expression = 'A*100/B'; - const disabled = true; return getQueryBuilderQuerieswithFormula({ - metricNameA, - metricNameB, + autocompleteDataA, + autocompleteDataB, additionalItemsA, additionalItemsB, - legend, - disabled, - expression, - legendFormula, + legend: GraphTitle.ERROR_PERCENTAGE, + disabled: true, + expression: FORMULA.ERROR_PERCENTAGE, + legendFormula: GraphTitle.ERROR_PERCENTAGE, }); }; export interface OperationPerSecProps { - servicename: string | undefined; + servicename: IServiceName['servicename']; tagFilterItems: TagFilterItem[]; topLevelOperations: string[]; } + +export interface LatencyProps { + servicename: IServiceName['servicename']; + tagFilterItems: TagFilterItem[]; + isSpanMetricEnable?: boolean; + topLevelOperationsRoute: string[]; +} diff --git a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx index 44724ceb01..e2fd6d240f 100644 --- a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx @@ -1,5 +1,5 @@ import { Col } from 'antd'; -import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder'; +import Graph from 'container/GridGraphLayout/Graph/'; import { databaseCallsAvgDuration, databaseCallsRPS, @@ -15,9 +15,11 @@ import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; import { v4 as uuid } from 'uuid'; +import { GraphTitle } from '../constant'; import { getWidgetQueryBuilder } from '../MetricsApplication.factory'; -import { Card, GraphContainer, GraphTitle, Row } from '../styles'; +import { Card, GraphContainer, Row } from '../styles'; import { Button } from './styles'; +import { IServiceName } from './types'; import { dbSystemTags, handleNonInQueryRange, @@ -26,7 +28,7 @@ import { } from './util'; function DBCall(): JSX.Element { - const { servicename } = useParams<{ servicename?: string }>(); + const { servicename } = useParams(); const [selectedTimeStamp, setSelectedTimeStamp] = useState(0); const { queries } = useResourceAttribute(); @@ -48,31 +50,37 @@ function DBCall(): JSX.Element { const databaseCallsRPSWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: databaseCallsRPS({ - servicename, - legend, - tagFilterItems, - }), - clickhouse_sql: [], - id: uuid(), - }), + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: databaseCallsRPS({ + servicename, + legend, + tagFilterItems, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.DATABASE_CALLS_RPS, + ), [servicename, tagFilterItems], ); const databaseCallsAverageDurationWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: databaseCallsAvgDuration({ - servicename, - tagFilterItems, - }), - clickhouse_sql: [], - id: uuid(), - }), + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: databaseCallsAvgDuration({ + servicename, + tagFilterItems, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.DATABASE_CALLS_AVG_DURATION, + ), [servicename, tagFilterItems], ); @@ -92,11 +100,9 @@ function DBCall(): JSX.Element { View Traces - Database Calls RPS - { @@ -108,6 +114,9 @@ function DBCall(): JSX.Element { 'database_call_rps', ); }} + allowClone={false} + allowDelete={false} + allowEdit={false} /> @@ -127,11 +136,9 @@ function DBCall(): JSX.Element { View Traces - Database Calls Avg Duration - { @@ -143,6 +150,9 @@ function DBCall(): JSX.Element { 'database_call_avg_duration', ); }} + allowClone={false} + allowDelete={false} + allowEdit={false} /> diff --git a/frontend/src/container/MetricsApplication/Tabs/External.tsx b/frontend/src/container/MetricsApplication/Tabs/External.tsx index 36f3235730..ab1e99f430 100644 --- a/frontend/src/container/MetricsApplication/Tabs/External.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/External.tsx @@ -1,5 +1,5 @@ import { Col } from 'antd'; -import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder'; +import Graph from 'container/GridGraphLayout/Graph/'; import { externalCallDuration, externalCallDurationByAddress, @@ -16,10 +16,11 @@ import { useParams } from 'react-router-dom'; import { EQueryType } from 'types/common/dashboard'; import { v4 as uuid } from 'uuid'; +import { GraphTitle, legend } from '../constant'; import { getWidgetQueryBuilder } from '../MetricsApplication.factory'; -import { Card, GraphContainer, GraphTitle, Row } from '../styles'; -import { legend } from './constant'; +import { Card, GraphContainer, Row } from '../styles'; import { Button } from './styles'; +import { IServiceName } from './types'; import { handleNonInQueryRange, onGraphClickHandler, @@ -29,7 +30,7 @@ import { function External(): JSX.Element { const [selectedTimeStamp, setSelectedTimeStamp] = useState(0); - const { servicename } = useParams<{ servicename?: string }>(); + const { servicename } = useParams(); const { queries } = useResourceAttribute(); const tagFilterItems = useMemo( @@ -40,17 +41,20 @@ function External(): JSX.Element { const externalCallErrorWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: externalCallErrorPercent({ - servicename, - legend: legend.address, - tagFilterItems, - }), - clickhouse_sql: [], - id: uuid(), - }), + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: externalCallErrorPercent({ + servicename, + legend: legend.address, + tagFilterItems, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE, + ), [servicename, tagFilterItems], ); @@ -61,48 +65,57 @@ function External(): JSX.Element { const externalCallDurationWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: externalCallDuration({ - servicename, - tagFilterItems, - }), - clickhouse_sql: [], - id: uuid(), - }), + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: externalCallDuration({ + servicename, + tagFilterItems, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.EXTERNAL_CALL_DURATION, + ), [servicename, tagFilterItems], ); const externalCallRPSWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: externalCallRpsByAddress({ - servicename, - legend: legend.address, - tagFilterItems, - }), - clickhouse_sql: [], - id: uuid(), - }), + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: externalCallRpsByAddress({ + servicename, + legend: legend.address, + tagFilterItems, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS, + ), [servicename, tagFilterItems], ); const externalCallDurationAddressWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: externalCallDurationByAddress({ - servicename, - legend: legend.address, - tagFilterItems, - }), - clickhouse_sql: [], - id: uuid(), - }), + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: externalCallDurationByAddress({ + servicename, + legend: legend.address, + tagFilterItems, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS, + ), [servicename, tagFilterItems], ); @@ -124,11 +137,9 @@ function External(): JSX.Element { View Traces - External Call Error Percentage - { @@ -140,6 +151,9 @@ function External(): JSX.Element { 'external_call_error_percentage', ); }} + allowClone={false} + allowDelete={false} + allowEdit={false} /> @@ -161,11 +175,9 @@ function External(): JSX.Element { - External Call duration - { @@ -177,6 +189,9 @@ function External(): JSX.Element { 'external_call_duration', ); }} + allowClone={false} + allowDelete={false} + allowEdit={false} /> @@ -199,11 +214,9 @@ function External(): JSX.Element { View Traces - External Call RPS(by Address) - { @@ -215,6 +228,9 @@ function External(): JSX.Element { 'external_call_rps_by_address', ); }} + allowClone={false} + allowDelete={false} + allowEdit={false} /> @@ -236,11 +252,9 @@ function External(): JSX.Element { - External Call duration(by Address) - { @@ -252,6 +266,9 @@ function External(): JSX.Element { 'external_call_duration_by_address', ); }} + allowClone={false} + allowDelete={false} + allowEdit={false} /> diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx index 5cd571a548..9d30b624f2 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx @@ -1,16 +1,9 @@ -import { Typography } from 'antd'; -import getServiceOverview from 'api/metrics/getServiceOverview'; import getTopLevelOperations, { ServiceDataProps, } from 'api/metrics/getTopLevelOperations'; -import getTopOperations from 'api/metrics/getTopOperations'; -import axios from 'axios'; import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js'; -import Graph from 'components/Graph'; -import Spinner from 'components/Spinner'; import { QueryParams } from 'constants/query'; import ROUTES from 'constants/routes'; -import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder'; import { routeConfig } from 'container/SideNav/config'; import { getQueryString } from 'container/SideNav/helper'; import useResourceAttribute from 'hooks/useResourceAttribute'; @@ -18,32 +11,30 @@ import { convertRawQueriesToTraceSelectedTags, resourceAttributesToTagFilterItems, } from 'hooks/useResourceAttribute/utils'; -import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond'; -import { colors } from 'lib/getRandomColor'; -import getStep from 'lib/getStep'; import history from 'lib/history'; import { useCallback, useMemo, useState } from 'react'; -import { useQueries, UseQueryResult } from 'react-query'; +import { useQuery } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { useLocation, useParams } from 'react-router-dom'; import { UpdateTimeInterval } from 'store/actions'; import { AppState } from 'store/reducers'; -import { PayloadProps } from 'types/api/metrics/getServiceOverview'; -import { PayloadProps as PayloadPropsTopOpertions } from 'types/api/metrics/getTopOperations'; import { EQueryType } from 'types/common/dashboard'; import { GlobalReducer } from 'types/reducer/globalTime'; import { Tags } from 'types/reducer/trace'; import { v4 as uuid } from 'uuid'; -import { SOMETHING_WENT_WRONG } from '../../../constants/api'; +import { GraphTitle } from '../constant'; import { getWidgetQueryBuilder } from '../MetricsApplication.factory'; import { errorPercentage, operationPerSec, } from '../MetricsPageQueries/OverviewQueries'; -import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles'; -import TopOperationsTable from '../TopOperationsTable'; +import { Col, Row } from '../styles'; +import ServiceOverview from './Overview/ServiceOverview'; +import TopLevelOperation from './Overview/TopLevelOperations'; +import TopOperation from './Overview/TopOperation'; import { Button } from './styles'; +import { IServiceName } from './types'; import { handleNonInQueryRange, onGraphClickHandler, @@ -54,7 +45,7 @@ function Application(): JSX.Element { const { maxTime, minTime } = useSelector( (state) => state.globalTime, ); - const { servicename } = useParams<{ servicename?: string }>(); + const { servicename } = useParams(); const [selectedTimeStamp, setSelectedTimeStamp] = useState(0); const { search } = useLocation(); const { queries } = useResourceAttribute(); @@ -86,53 +77,15 @@ function Application(): JSX.Element { [handleSetTimeStamp], ); - const queryResult = useQueries< - [ - UseQueryResult, - UseQueryResult, - UseQueryResult, - ] - >([ - { - queryKey: [servicename, selectedTags, minTime, maxTime], - queryFn: (): Promise => - getServiceOverview({ - service: servicename || '', - start: minTime, - end: maxTime, - step: getStep({ - start: minTime, - end: maxTime, - inputFormat: 'ns', - }), - selectedTags, - }), - }, - { - queryKey: [minTime, maxTime, servicename, selectedTags], - queryFn: (): Promise => - getTopOperations({ - service: servicename || '', - start: minTime, - end: maxTime, - selectedTags, - }), - }, - { - queryKey: [servicename, minTime, maxTime, selectedTags], - queryFn: (): Promise => getTopLevelOperations(), - }, - ]); - - const serviceOverview = queryResult[0].data; - const serviceOverviewError = queryResult[0].error; - const serviceOverviewIsError = queryResult[0].isError; - const serviceOverviewIsLoading = queryResult[0].isLoading; - const topOperations = queryResult[1].data; - const topLevelOperations = queryResult[2].data; - const topLevelOperationsError = queryResult[2].error; - const topLevelOperationsIsError = queryResult[2].isError; - const topLevelOperationsIsLoading = queryResult[2].isLoading; + const { + data: topLevelOperations, + isLoading: topLevelOperationsLoading, + error: topLevelOperationsError, + isError: topLevelOperationsIsError, + } = useQuery({ + queryKey: [servicename, minTime, maxTime, selectedTags], + queryFn: getTopLevelOperations, + }); const selectedTraceTags: string = JSON.stringify( convertRawQueriesToTraceSelectedTags(queries) || [], @@ -144,40 +97,47 @@ function Application(): JSX.Element { [queries], ); + const topLevelOperationsRoute = useMemo( + () => (topLevelOperations ? topLevelOperations[servicename || ''] : []), + [servicename, topLevelOperations], + ); + const operationPerSecWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: operationPerSec({ - servicename, - tagFilterItems, - topLevelOperations: topLevelOperations - ? topLevelOperations[servicename || ''] - : [], - }), - clickhouse_sql: [], - id: uuid(), - }), - [servicename, topLevelOperations, tagFilterItems], + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: operationPerSec({ + servicename, + tagFilterItems, + topLevelOperations: topLevelOperationsRoute, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.RATE_PER_OPS, + ), + [servicename, tagFilterItems, topLevelOperationsRoute], ); const errorPercentageWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: errorPercentage({ - servicename, - tagFilterItems, - topLevelOperations: topLevelOperations - ? topLevelOperations[servicename || ''] - : [], - }), - clickhouse_sql: [], - id: uuid(), - }), - [servicename, topLevelOperations, tagFilterItems], + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: errorPercentage({ + servicename, + tagFilterItems, + topLevelOperations: topLevelOperationsRoute, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.ERROR_PERCENTAGE, + ), + [servicename, tagFilterItems, topLevelOperationsRoute], ); const onDragSelect = useCallback( @@ -212,107 +172,18 @@ function Application(): JSX.Element { ); }; - const generalChartDataProperties = useCallback( - (title: string, colorIndex: number) => ({ - borderColor: colors[colorIndex], - label: title, - showLine: true, - borderWidth: 1.5, - spanGaps: true, - pointRadius: 2, - pointHoverRadius: 4, - }), - [], - ); - - const dataSets = useMemo(() => { - if (!serviceOverview) { - return []; - } - - return [ - { - data: serviceOverview.map((e) => - parseFloat(convertToNanoSecondsToSecond(e.p99)), - ), - ...generalChartDataProperties('p99 Latency', 0), - }, - { - data: serviceOverview.map((e) => - parseFloat(convertToNanoSecondsToSecond(e.p95)), - ), - ...generalChartDataProperties('p95 Latency', 1), - }, - { - data: serviceOverview.map((e) => - parseFloat(convertToNanoSecondsToSecond(e.p50)), - ), - ...generalChartDataProperties('p50 Latency', 2), - }, - ]; - }, [generalChartDataProperties, serviceOverview]); - - const data = useMemo(() => { - if (!serviceOverview) { - return { - datasets: [], - labels: [], - }; - } - - return { - datasets: dataSets, - labels: serviceOverview.map( - (e) => new Date(parseFloat(convertToNanoSecondsToSecond(e.timestamp))), - ), - }; - }, [serviceOverview, dataSets]); - return ( <> - - - {serviceOverviewIsError ? ( - - {axios.isAxiosError(serviceOverviewError) - ? serviceOverviewError.response?.data - : SOMETHING_WENT_WRONG} - - ) : ( - <> - Latency - {serviceOverviewIsLoading && ( - - )} - {!serviceOverviewIsLoading && ( - - - - )} - - )} - + @@ -328,30 +199,17 @@ function Application(): JSX.Element { > View Traces - - {topLevelOperationsIsError ? ( - - {axios.isAxiosError(topLevelOperationsError) - ? topLevelOperationsError.response?.data - : SOMETHING_WENT_WRONG} - - ) : ( - <> - Rate (ops/s) - - - - - )} - + @@ -367,43 +225,28 @@ function Application(): JSX.Element { View Traces - - {topLevelOperationsIsError ? ( - - {axios.isAxiosError(topLevelOperationsError) - ? topLevelOperationsError.response?.data - : SOMETHING_WENT_WRONG} - - ) : ( - <> - Error Percentage - - - - - )} - + - - - + ); } -type ClickHandlerType = ( +export type ClickHandlerType = ( ChartEvent: ChartEvent, activeElements: ActiveElement[], chart: Chart, diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx new file mode 100644 index 0000000000..3b1ad2b7d9 --- /dev/null +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx @@ -0,0 +1,93 @@ +import { FeatureKeys } from 'constants/features'; +import Graph from 'container/GridGraphLayout/Graph/'; +import { GraphTitle } from 'container/MetricsApplication/constant'; +import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; +import { latency } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries'; +import { Card, GraphContainer } from 'container/MetricsApplication/styles'; +import useFeatureFlag from 'hooks/useFeatureFlag'; +import { useMemo } from 'react'; +import { useParams } from 'react-router-dom'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { v4 as uuid } from 'uuid'; + +import { ClickHandlerType } from '../Overview'; +import { Button } from '../styles'; +import { IServiceName } from '../types'; +import { onViewTracePopupClick } from '../util'; + +function ServiceOverview({ + onDragSelect, + handleGraphClick, + selectedTraceTags, + selectedTimeStamp, + tagFilterItems, + topLevelOperationsRoute, +}: ServiceOverviewProps): JSX.Element { + const { servicename } = useParams(); + + const isSpanMetricEnable = useFeatureFlag(FeatureKeys.USE_SPAN_METRICS) + ?.active; + + const latencyWidget = useMemo( + () => + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: latency({ + servicename, + tagFilterItems, + isSpanMetricEnable, + topLevelOperationsRoute, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.LATENCY, + ), + [servicename, tagFilterItems, isSpanMetricEnable, topLevelOperationsRoute], + ); + + return ( + <> + + + + + + + + ); +} + +interface ServiceOverviewProps { + selectedTimeStamp: number; + selectedTraceTags: string; + onDragSelect: (start: number, end: number) => void; + handleGraphClick: (type: string) => ClickHandlerType; + tagFilterItems: TagFilterItem[]; + topLevelOperationsRoute: string[]; +} + +export default ServiceOverview; diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/TopLevelOperations.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/TopLevelOperations.tsx new file mode 100644 index 0000000000..6d4a624a04 --- /dev/null +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/TopLevelOperations.tsx @@ -0,0 +1,65 @@ +import { Typography } from 'antd'; +import axios from 'axios'; +import Spinner from 'components/Spinner'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import Graph from 'container/GridGraphLayout/Graph/'; +import { Card, GraphContainer } from 'container/MetricsApplication/styles'; +import { Widgets } from 'types/api/dashboard/getAll'; + +import { ClickHandlerType } from '../Overview'; + +function TopLevelOperation({ + name, + opName, + topLevelOperationsIsError, + topLevelOperationsError, + topLevelOperationsLoading, + onDragSelect, + handleGraphClick, + widget, + yAxisUnit, +}: TopLevelOperationProps): JSX.Element { + return ( + + {topLevelOperationsIsError ? ( + + {axios.isAxiosError(topLevelOperationsError) + ? topLevelOperationsError.response?.data + : SOMETHING_WENT_WRONG} + + ) : ( + + {topLevelOperationsLoading && ( + + )} + {!topLevelOperationsLoading && ( + + )} + + )} + + ); +} + +interface TopLevelOperationProps { + name: string; + opName: string; + topLevelOperationsIsError: boolean; + topLevelOperationsError: unknown; + topLevelOperationsLoading: boolean; + onDragSelect: (start: number, end: number) => void; + handleGraphClick: (type: string) => ClickHandlerType; + widget: Widgets; + yAxisUnit: string; +} + +export default TopLevelOperation; diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperation.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperation.tsx new file mode 100644 index 0000000000..183ec20e7a --- /dev/null +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperation.tsx @@ -0,0 +1,46 @@ +import getTopOperations from 'api/metrics/getTopOperations'; +import Spinner from 'components/Spinner'; +import { Card } from 'container/MetricsApplication/styles'; +import TopOperationsTable from 'container/MetricsApplication/TopOperationsTable'; +import useResourceAttribute from 'hooks/useResourceAttribute'; +import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; +import { useMemo } from 'react'; +import { useQuery } from 'react-query'; +import { useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import { PayloadProps } from 'types/api/metrics/getTopOperations'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { Tags } from 'types/reducer/trace'; + +function TopOperation(): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + const { servicename } = useParams<{ servicename?: string }>(); + const { queries } = useResourceAttribute(); + const selectedTags = useMemo( + () => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [], + [queries], + ); + + const { data, isLoading } = useQuery({ + queryKey: [minTime, maxTime, servicename, selectedTags], + queryFn: (): Promise => + getTopOperations({ + service: servicename || '', + start: minTime, + end: maxTime, + selectedTags, + }), + }); + + return ( + + {isLoading && } + {!isLoading && } + + ); +} + +export default TopOperation; diff --git a/frontend/src/container/MetricsApplication/Tabs/constant.ts b/frontend/src/container/MetricsApplication/Tabs/constant.ts deleted file mode 100644 index 4931667e6e..0000000000 --- a/frontend/src/container/MetricsApplication/Tabs/constant.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const legend = { - address: '{{address}}', -}; diff --git a/frontend/src/container/MetricsApplication/Tabs/types.ts b/frontend/src/container/MetricsApplication/Tabs/types.ts new file mode 100644 index 0000000000..2d60b132ee --- /dev/null +++ b/frontend/src/container/MetricsApplication/Tabs/types.ts @@ -0,0 +1,3 @@ +export interface IServiceName { + servicename: string; +} diff --git a/frontend/src/container/MetricsApplication/constant.ts b/frontend/src/container/MetricsApplication/constant.ts new file mode 100644 index 0000000000..0e917cce47 --- /dev/null +++ b/frontend/src/container/MetricsApplication/constant.ts @@ -0,0 +1,60 @@ +export const legend = { + address: '{{address}}', +}; + +export const QUERYNAME_AND_EXPRESSION = ['A', 'B', 'C']; +export const LATENCY_AGGREGATEOPERATOR = ['p50', 'p90', 'p99']; +export const LATENCY_AGGREGATEOPERATOR_SPAN_METRICS = [ + 'hist_quantile_50', + 'hist_quantile_90', + 'hist_quantile_99', +]; +export const OPERATION_LEGENDS = ['Operations']; + +export enum FORMULA { + ERROR_PERCENTAGE = 'A*100/B', + DATABASE_CALLS_AVG_DURATION = 'A/B', +} + +export enum GraphTitle { + LATENCY = 'Latency', + RATE_PER_OPS = 'Rate (ops/s)', + ERROR_PERCENTAGE = 'Error Percentage', + DATABASE_CALLS_RPS = 'Database Calls RPS', + DATABASE_CALLS_AVG_DURATION = 'Database Calls Avg Duration', + EXTERNAL_CALL_ERROR_PERCENTAGE = 'External Call Error Percentage', + EXTERNAL_CALL_DURATION = 'External Call duration', + EXTERNAL_CALL_RPS_BY_ADDRESS = 'External Call RPS(by Address)', + EXTERNAL_CALL_DURATION_BY_ADDRESS = 'External Call duration(by Address)', +} + +export enum DataType { + STRING = 'string', + FLOAT64 = 'float64', + INT64 = 'int64', +} + +export enum MetricsType { + Tag = 'tag', + Resource = 'resource', +} + +export enum WidgetKeys { + Name = 'name', + Address = 'address', + DurationNano = 'durationNano', + StatusCode = 'status_code', + Operation = 'operation', + OperationName = 'operationName', + Service_name = 'service_name', + ServiceName = 'serviceName', + SignozLatencyCount = 'signoz_latency_count', + SignozDBLatencyCount = 'signoz_db_latency_count', + DatabaseCallCount = 'signoz_database_call_count', + DatabaseCallLatencySum = 'signoz_database_call_latency_sum', + SignozDbLatencySum = 'signoz_db_latency_sum', + SignozCallsTotal = 'signoz_calls_total', + SignozExternalCallLatencyCount = 'signoz_external_call_latency_count', + SignozExternalCallLatencySum = 'signoz_external_call_latency_sum', + Signoz_latency_bucket = 'signoz_latency_bucket', +} diff --git a/frontend/src/container/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts index 96094a7995..6bc1700348 100644 --- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts +++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts @@ -3,12 +3,12 @@ import getFromLocalstorage from 'api/browser/localstorage/get'; import setToLocalstorage from 'api/browser/localstorage/set'; import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; import { LOCALSTORAGE } from 'constants/localStorage'; -import { QueryBuilderKeys } from 'constants/queryBuilder'; +import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import useDebounce from 'hooks/useDebounce'; import { useNotifications } from 'hooks/useNotifications'; import useUrlQueryData from 'hooks/useUrlQueryData'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useQueries, useQuery } from 'react-query'; +import { useQueries } from 'react-query'; import { ErrorResponse, SuccessResponse } from 'types/api'; import { BaseAutocompleteData, @@ -30,6 +30,7 @@ interface UseOptionsMenuProps { interface UseOptionsMenu { options: OptionsQuery; config: OptionsMenuConfig; + handleOptionsChange: (newQueryData: OptionsQuery) => void; } const useOptionsMenu = ({ @@ -115,16 +116,12 @@ const useOptionsMenu = ({ const { data: searchedAttributesData, isFetching: isSearchedAttributesFetching, - } = useQuery( - [QueryBuilderKeys.GET_AGGREGATE_KEYS, debouncedSearchText, isFocused], - async () => - getAggregateKeys({ - ...initialQueryParams, - searchText: debouncedSearchText, - }), + } = useGetAggregateKeys( { - enabled: isFocused, + ...initialQueryParams, + searchText: debouncedSearchText, }, + { queryKey: [debouncedSearchText, isFocused], enabled: isFocused }, ); const searchedAttributeKeys = useMemo( @@ -306,6 +303,7 @@ const useOptionsMenu = ({ return { options: optionsQueryData, config: optionsMenuConfig, + handleOptionsChange: handleRedirectWithOptionsData, }; }; diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts index f14dfee184..fec93c7106 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts +++ b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts @@ -3,6 +3,8 @@ import { ReactNode } from 'react'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; +import { OrderByFilterProps } from './filters/OrderByFilter/OrderByFilter.interfaces'; + export type QueryBuilderConfig = | { queryVariant: 'static'; @@ -17,4 +19,5 @@ export type QueryBuilderProps = { filterConfigs?: Partial< Record >; + queryComponents?: { renderOrderBy?: (props: OrderByFilterProps) => ReactNode }; }; diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/QueryBuilder/QueryBuilder.tsx index aa3cbae646..35e709113e 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.tsx +++ b/frontend/src/container/QueryBuilder/QueryBuilder.tsx @@ -16,6 +16,7 @@ export const QueryBuilder = memo(function QueryBuilder({ panelType: newPanelType, actions, filterConfigs = {}, + queryComponents, }: QueryBuilderProps): JSX.Element { const { currentQuery, @@ -74,6 +75,7 @@ export const QueryBuilder = memo(function QueryBuilder({ queryVariant={config?.queryVariant || 'dropdown'} query={query} filterConfigs={filterConfigs} + queryComponents={queryComponents} /> ))} diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts index a2b6a55787..e468b62fe0 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts +++ b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts @@ -6,4 +6,4 @@ export type QueryProps = { isAvailableToDisable: boolean; query: IBuilderQuery; queryVariant: 'static' | 'dropdown'; -} & Pick; +} & Pick; diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index 2eba663e34..e60c562200 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -36,6 +36,7 @@ export const Query = memo(function Query({ queryVariant, query, filterConfigs, + queryComponents, }: QueryProps): JSX.Element { const { panelType } = useQueryBuilder(); const { @@ -110,6 +111,17 @@ export const Query = memo(function Query({ [handleChangeQueryData], ); + const renderOrderByFilter = useCallback((): ReactNode => { + if (queryComponents?.renderOrderBy) { + return queryComponents.renderOrderBy({ + query, + onChange: handleChangeOrderByKeys, + }); + } + + return ; + }, [queryComponents, query, handleChangeOrderByKeys]); + const renderAggregateEveryFilter = useCallback( (): JSX.Element | null => !filterConfigs?.stepInterval?.isHidden ? ( @@ -167,9 +179,7 @@ export const Query = memo(function Query({ - - - + {renderOrderByFilter()} )} @@ -225,9 +235,7 @@ export const Query = memo(function Query({ - - - + {renderOrderByFilter()} @@ -238,11 +246,11 @@ export const Query = memo(function Query({ } }, [ panelType, - query, isMetricsDataSource, - handleChangeHavingFilter, + query, handleChangeLimit, - handleChangeOrderByKeys, + handleChangeHavingFilter, + renderOrderByFilter, renderAggregateEveryFilter, ]); diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx index a41d5005d5..5be89bcf2b 100644 --- a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx @@ -7,6 +7,7 @@ import { selectValueDivider, } from 'constants/queryBuilder'; import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig'; +import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import useDebounce from 'hooks/useDebounce'; import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue'; // ** Components @@ -14,7 +15,7 @@ import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAut import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; import { isEqual, uniqWith } from 'lodash-es'; import { memo, useCallback, useEffect, useState } from 'react'; -import { useQuery, useQueryClient } from 'react-query'; +import { useQueryClient } from 'react-query'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { SelectOption } from 'types/common/select'; @@ -38,16 +39,15 @@ export const GroupByFilter = memo(function GroupByFilter({ const debouncedValue = useDebounce(searchText, DEBOUNCE_DELAY); - const { isFetching } = useQuery( - [QueryBuilderKeys.GET_AGGREGATE_KEYS, debouncedValue, isFocused], - async () => - getAggregateKeys({ - aggregateAttribute: query.aggregateAttribute.key, - dataSource: query.dataSource, - aggregateOperator: query.aggregateOperator, - searchText: debouncedValue, - }), + const { isFetching } = useGetAggregateKeys( { + aggregateAttribute: query.aggregateAttribute.key, + dataSource: query.dataSource, + aggregateOperator: query.aggregateOperator, + searchText: debouncedValue, + }, + { + queryKey: [debouncedValue, isFocused], enabled: !disabled && isFocused, onSuccess: (data) => { const keys = query.groupBy.reduce((acc, item) => { diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx index 7cacaa9877..4c1d42bdbc 100644 --- a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx @@ -1,208 +1,57 @@ 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'; -import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData'; +import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; +import { useMemo } from 'react'; 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, - getLabelFromValue, - mapLabelValuePairs, - orderByValueDelimiter, - splitOrderByFromString, - transformToOrderByStringValues, -} from './utils'; +import { useOrderByFilter } from './useOrderByFilter'; export function OrderByFilter({ query, onChange, }: OrderByFilterProps): JSX.Element { - const [searchText, setSearchText] = useState(''); - const [selectedValue, setSelectedValue] = useState( - transformToOrderByStringValues(query.orderBy), + const { + debouncedSearchText, + selectedValue, + aggregationOptions, + generateOptions, + createOptions, + handleChange, + handleSearchKeys, + } = useOrderByFilter({ query, onChange }); + + const { data, isFetching } = useGetAggregateKeys( + { + aggregateAttribute: query.aggregateAttribute.key, + dataSource: query.dataSource, + aggregateOperator: query.aggregateOperator, + searchText: debouncedSearchText, + }, + { + enabled: !!query.aggregateAttribute.key, + keepPreviousData: true, + }, ); - const { data, isFetching } = useQuery( - [QueryBuilderKeys.GET_AGGREGATE_KEYS, searchText], - async () => - getAggregateKeys({ - aggregateAttribute: query.aggregateAttribute.key, - dataSource: query.dataSource, - aggregateOperator: query.aggregateOperator, - searchText, - }), - { enabled: !!query.aggregateAttribute.key, keepPreviousData: true }, - ); - - const handleSearchKeys = useCallback( - (searchText: string): void => setSearchText(searchText), - [], - ); - - const noAggregationOptions = useMemo( - () => - data?.payload?.attributeKeys - ? mapLabelValuePairs(data?.payload?.attributeKeys).flat() - : [], - [data?.payload?.attributeKeys], - ); - - const aggregationOptions = useMemo( - () => - mapLabelValuePairs(query.groupBy) - .flat() - .concat([ - { - label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.ASC}`, - value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}${FILTERS.ASC}`, - }, - { - 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 keyOptions = createOptions(data?.payload?.attributeKeys || []); + const groupByOptions = createOptions(query.groupBy); const options = query.aggregateOperator === MetricAggregateOperator.NOOP - ? noAggregationOptions - : aggregationOptions; + ? keyOptions + : [...groupByOptions, ...aggregationOptions]; - const resultOption = [...customValue, ...options]; - - return resultOption.filter( - (option) => - !getLabelFromValue(selectedValue).includes( - getRemoveOrderFromValue(option.value), - ), - ); + return generateOptions(options); }, [ aggregationOptions, - customValue, - noAggregationOptions, + createOptions, + data?.payload?.attributeKeys, + generateOptions, query.aggregateOperator, - selectedValue, + query.groupBy, ]); - 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 getValidResult = useCallback( - (result: IOption[]): IOption[] => - result.reduce((acc, item) => { - if (item.value === FILTERS.ASC || item.value === FILTERS.DESC) return acc; - - if (item.value.includes(FILTERS.ASC) || item.value.includes(FILTERS.DESC)) { - const splittedOrderBy = splitOrderByFromString(item.value); - - if (splittedOrderBy) { - acc.push({ - label: `${splittedOrderBy.columnName} ${splittedOrderBy.order}`, - value: `${splittedOrderBy.columnName}${orderByValueDelimiter}${splittedOrderBy.order}`, - }); - - return acc; - } - } - - acc.push(item); - - return acc; - }, []), - [], - ); - - const handleChange = (values: IOption[]): void => { - const validResult = getValidResult(values); - const result = getUniqValues(validResult); - - const orderByValues: OrderByPayload[] = result.map((item) => { - const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter }); - - if (!match) { - return { - columnName: item.value, - order: 'asc', - }; - } - - const [columnName, order] = match.data.flat() as string[]; - - const columnNameValue = checkIfKeyPresent( - columnName, - query.aggregateAttribute.key, - ) - ? '#SIGNOZ_VALUE' - : columnName; - - const orderValue = order ?? 'asc'; - - return { - columnName: columnNameValue, - order: orderValue, - }; - }); - - const selectedValue: IOption[] = orderByValues.map((item) => ({ - label: `${item.columnName} ${item.order}`, - value: `${item.columnName} ${item.order}`, - })); - - setSelectedValue(selectedValue); - - setSearchText(''); - onChange(orderByValues); - }; - const isDisabledSelect = useMemo( () => !query.aggregateAttribute.key || diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/constants.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/constants.ts new file mode 100644 index 0000000000..d9f1efa4bf --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/constants.ts @@ -0,0 +1 @@ +export const SIGNOZ_VALUE = '#SIGNOZ_VALUE'; diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/useOrderByFilter.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/useOrderByFilter.ts new file mode 100644 index 0000000000..8d8fae7e0f --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/useOrderByFilter.ts @@ -0,0 +1,199 @@ +import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig'; +import useDebounce from 'hooks/useDebounce'; +import { IOption } from 'hooks/useResourceAttribute/types'; +import { isEqual, uniqWith } from 'lodash-es'; +import * as Papa from 'papaparse'; +import { useCallback, useMemo, useState } from 'react'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData'; + +import { getRemoveOrderFromValue } from '../QueryBuilderSearch/utils'; +import { FILTERS } from './config'; +import { SIGNOZ_VALUE } from './constants'; +import { OrderByFilterProps } from './OrderByFilter.interfaces'; +import { + getLabelFromValue, + mapLabelValuePairs, + orderByValueDelimiter, + splitOrderByFromString, + transformToOrderByStringValues, +} from './utils'; + +type UseOrderByFilterResult = { + searchText: string; + debouncedSearchText: string; + selectedValue: IOption[]; + aggregationOptions: IOption[]; + generateOptions: (options: IOption[]) => IOption[]; + createOptions: (data: BaseAutocompleteData[]) => IOption[]; + handleChange: (values: IOption[]) => void; + handleSearchKeys: (search: string) => void; +}; + +export const useOrderByFilter = ({ + query, + onChange, +}: OrderByFilterProps): UseOrderByFilterResult => { + const [searchText, setSearchText] = useState(''); + + const debouncedSearchText = useDebounce(searchText, DEBOUNCE_DELAY); + + const handleSearchKeys = useCallback( + (searchText: string): void => setSearchText(searchText), + [], + ); + + 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 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 selectedValue = useMemo(() => transformToOrderByStringValues(query), [ + query, + ]); + + const generateOptions = useCallback( + (options: IOption[]): IOption[] => { + const currentCustomValue = options.find( + (keyOption) => + getRemoveOrderFromValue(keyOption.value) === debouncedSearchText, + ) + ? [] + : customValue; + + const result = [...currentCustomValue, ...options]; + + const uniqResult = uniqWith(result, isEqual); + + return uniqResult.filter( + (option) => + !getLabelFromValue(selectedValue).includes( + getRemoveOrderFromValue(option.value), + ), + ); + }, + [customValue, debouncedSearchText, selectedValue], + ); + + const getValidResult = useCallback( + (result: IOption[]): IOption[] => + result.reduce((acc, item) => { + if (item.value === FILTERS.ASC || item.value === FILTERS.DESC) return acc; + + if (item.value.includes(FILTERS.ASC) || item.value.includes(FILTERS.DESC)) { + const splittedOrderBy = splitOrderByFromString(item.value); + + if (splittedOrderBy) { + acc.push({ + label: `${splittedOrderBy.columnName} ${splittedOrderBy.order}`, + value: `${splittedOrderBy.columnName}${orderByValueDelimiter}${splittedOrderBy.order}`, + }); + + return acc; + } + } + + acc.push(item); + + return acc; + }, []), + [], + ); + + const handleChange = (values: IOption[]): void => { + const validResult = getValidResult(values); + const result = getUniqValues(validResult); + + const orderByValues: OrderByPayload[] = result.map((item) => { + const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter }); + + if (!match) { + return { + columnName: item.value, + order: 'asc', + }; + } + + const [columnName, order] = match.data.flat() as string[]; + + const columnNameValue = + columnName === SIGNOZ_VALUE ? SIGNOZ_VALUE : columnName; + + const orderValue = order ?? 'asc'; + + return { + columnName: columnNameValue, + order: orderValue, + }; + }); + + setSearchText(''); + onChange(orderByValues); + }; + + const createOptions = useCallback( + (data: BaseAutocompleteData[]): IOption[] => mapLabelValuePairs(data).flat(), + [], + ); + + const aggregationOptions = useMemo( + () => [ + { + label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.ASC}`, + value: `${SIGNOZ_VALUE}${orderByValueDelimiter}${FILTERS.ASC}`, + }, + { + label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.DESC}`, + value: `${SIGNOZ_VALUE}${orderByValueDelimiter}${FILTERS.DESC}`, + }, + ], + [query], + ); + + return { + searchText, + debouncedSearchText, + selectedValue, + aggregationOptions, + createOptions, + handleChange, + handleSearchKeys, + generateOptions, + }; +}; diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts index 345a2eaab9..d109744138 100644 --- a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts @@ -1,31 +1,32 @@ 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'; +import { + IBuilderQuery, + OrderByPayload, +} from 'types/api/queryBuilder/queryBuilderData'; import { FILTERS } from './config'; +import { SIGNOZ_VALUE } from './constants'; export const orderByValueDelimiter = '|'; export const transformToOrderByStringValues = ( - orderBy: OrderByPayload[], + query: IBuilderQuery, ): IOption[] => { - const prepareSelectedValue: IOption[] = orderBy.reduce( - (acc, item) => { - if (item.columnName === '#SIGNOZ_VALUE') return acc; - - const option: IOption = { - label: `${item.columnName} ${item.order}`, + const prepareSelectedValue: IOption[] = query.orderBy.map((item) => { + if (item.columnName === SIGNOZ_VALUE) { + return { + label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${item.order}`, value: `${item.columnName}${orderByValueDelimiter}${item.order}`, }; + } - acc.push(option); - - return acc; - }, - [], - ); + return { + label: `${item.columnName} ${item.order}`, + value: `${item.columnName}${orderByValueDelimiter}${item.order}`, + }; + }); return prepareSelectedValue; }; @@ -34,20 +35,15 @@ export function mapLabelValuePairs( arr: BaseAutocompleteData[], ): Array[] { return arr.map((item) => { - const label = transformStringWithPrefix({ - str: item.key, - prefix: item.type || '', - condition: !item.isColumn, - }); const value = item.key; return [ { - label: `${label} asc`, - value: `${value}${orderByValueDelimiter}asc`, + label: `${value} ${FILTERS.ASC}`, + value: `${value}${orderByValueDelimiter}${FILTERS.ASC}`, }, { - label: `${label} desc`, - value: `${value}${orderByValueDelimiter}desc`, + label: `${value} ${FILTERS.DESC}`, + value: `${value}${orderByValueDelimiter}${FILTERS.DESC}`, }, ]; }); @@ -58,6 +54,7 @@ export function getLabelFromValue(arr: IOption[]): string[] { const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter }); if (match) { const [key] = match.data as string[]; + return key[0]; } diff --git a/frontend/src/container/TracesExplorer/ListView/index.tsx b/frontend/src/container/TracesExplorer/ListView/index.tsx index 7e70b0adf8..4ff128b629 100644 --- a/frontend/src/container/TracesExplorer/ListView/index.tsx +++ b/frontend/src/container/TracesExplorer/ListView/index.tsx @@ -6,6 +6,8 @@ import { useOptionsMenu } from 'container/OptionsMenu'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { Pagination, URL_PAGINATION } from 'hooks/queryPagination'; +import useDragColumns from 'hooks/useDragColumns'; +import { getDraggedColumns } from 'hooks/useDragColumns/utils'; import useUrlQueryData from 'hooks/useUrlQueryData'; import history from 'lib/history'; import { RowData } from 'lib/query/createTableColumnsFromQuery'; @@ -37,6 +39,10 @@ function ListView(): JSX.Element { }, }); + const { draggedColumns, onDragColumns } = useDragColumns( + LOCALSTORAGE.TRACES_LIST_COLUMNS, + ); + const { queryData: paginationQueryData } = useUrlQueryData( URL_PAGINATION, ); @@ -82,9 +88,10 @@ function ListView(): JSX.Element { queryTableDataResult, ]); - const columns = useMemo(() => getListColumns(options?.selectColumns || []), [ - options?.selectColumns, - ]); + const columns = useMemo(() => { + const updatedColumns = getListColumns(options?.selectColumns || []); + return getDraggedColumns(updatedColumns, draggedColumns); + }, [options?.selectColumns, draggedColumns]); const transformedQueryTableData = useMemo( () => transformDataWithDate(queryTableData) || [], @@ -106,6 +113,12 @@ function ListView(): JSX.Element { [], ); + const handleDragColumn = useCallback( + (fromIndex: number, toIndex: number) => + onDragColumns(columns, fromIndex, toIndex), + [columns, onDragColumns], + ); + return ( )} diff --git a/frontend/src/container/TracesExplorer/QuerySection/index.tsx b/frontend/src/container/TracesExplorer/QuerySection/index.tsx index 177311dae0..81514b496d 100644 --- a/frontend/src/container/TracesExplorer/QuerySection/index.tsx +++ b/frontend/src/container/TracesExplorer/QuerySection/index.tsx @@ -1,10 +1,12 @@ import { Button } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; +import ExplorerOrderBy from 'container/ExplorerOrderBy'; import { QueryBuilder } from 'container/QueryBuilder'; +import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces'; import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { memo, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { DataSource } from 'types/common/queryBuilder'; import { ButtonWrapper, Container } from './styles'; @@ -22,6 +24,22 @@ function QuerySection(): JSX.Element { return config; }, []); + const renderOrderBy = useCallback( + ({ query, onChange }: OrderByFilterProps) => ( + + ), + [], + ); + + const queryComponents = useMemo((): QueryBuilderProps['queryComponents'] => { + const shouldRenderCustomOrderBy = + panelTypes === PANEL_TYPES.LIST || panelTypes === PANEL_TYPES.TRACE; + + return { + ...(shouldRenderCustomOrderBy ? { renderOrderBy } : {}), + }; + }, [panelTypes, renderOrderBy]); + return (
Empty