diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index ffa2a10f71..42dbd5afc6 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.18.0 + image: signoz/query-service:0.18.1 command: ["-config=/root/config/prometheus.yml"] # ports: # - "6060:6060" # pprof port @@ -166,7 +166,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:0.18.0 + image: signoz/frontend:0.18.1 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 a4d23311e7..9a967cc800 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.18.0} + image: signoz/query-service:${DOCKER_TAG:-0.18.1} 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.18.0} + image: signoz/frontend:${DOCKER_TAG:-0.18.1} container_name: frontend restart: on-failure depends_on: diff --git a/frontend/src/api/apiV1.ts b/frontend/src/api/apiV1.ts index 5145443b2a..2e7df02395 100644 --- a/frontend/src/api/apiV1.ts +++ b/frontend/src/api/apiV1.ts @@ -1,6 +1,7 @@ const apiV1 = '/api/v1/'; export const apiV2 = '/api/v2/'; +export const apiV3 = '/api/v3/'; export const apiAlertManager = '/api/alertmanager'; export default apiV1; diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 2a48730f04..584e3b4868 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -9,7 +9,7 @@ import { ENVIRONMENT } from 'constants/env'; import { LOCALSTORAGE } from 'constants/localStorage'; import store from 'store'; -import apiV1, { apiAlertManager, apiV2 } from './apiV1'; +import apiV1, { apiAlertManager, apiV2, apiV3 } from './apiV1'; import { Logout } from './utils'; const interceptorsResponse = ( @@ -109,6 +109,17 @@ ApiV2Instance.interceptors.response.use( ); ApiV2Instance.interceptors.request.use(interceptorsRequestResponse); +// axios V3 +export const ApiV3Instance = axios.create({ + baseURL: `${ENVIRONMENT.baseURL}${apiV3}`, +}); +ApiV3Instance.interceptors.response.use( + interceptorsResponse, + interceptorRejected, +); +ApiV3Instance.interceptors.request.use(interceptorsRequestResponse); +// + AxiosAlertManagerInstance.interceptors.response.use( interceptorsResponse, interceptorRejected, diff --git a/frontend/src/api/queryBuilder/getAggregateAttribute.ts b/frontend/src/api/queryBuilder/getAggregateAttribute.ts new file mode 100644 index 0000000000..15e221d975 --- /dev/null +++ b/frontend/src/api/queryBuilder/getAggregateAttribute.ts @@ -0,0 +1,33 @@ +import { ApiV3Instance } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError, AxiosResponse } from 'axios'; +// ** Helpers +import { ErrorResponse, SuccessResponse } from 'types/api'; +// ** Types +import { IGetAggregateAttributePayload } from 'types/api/queryBuilder/getAggregatorAttribute'; +import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse'; + +export const getAggregateAttribute = async ({ + aggregateOperator, + searchText, + dataSource, +}: IGetAggregateAttributePayload): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response: AxiosResponse<{ + data: IQueryAutocompleteResponse; + }> = await ApiV3Instance.get( + `autocomplete/aggregate_attributes?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&searchText=${searchText}`, + ); + + return { + statusCode: 200, + error: null, + message: response.statusText, + payload: response.data.data, + }; + } catch (e) { + return ErrorResponseHandler(e as AxiosError); + } +}; diff --git a/frontend/src/api/queryBuilder/getAttributeKeys.ts b/frontend/src/api/queryBuilder/getAttributeKeys.ts new file mode 100644 index 0000000000..b42566a75e --- /dev/null +++ b/frontend/src/api/queryBuilder/getAttributeKeys.ts @@ -0,0 +1,33 @@ +import { ApiV3Instance } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError, AxiosResponse } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +// ** Types +import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys'; +import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse'; + +export const getAggregateKeys = async ({ + aggregateOperator, + searchText, + dataSource, + aggregateAttribute, +}: IGetAttributeKeysPayload): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response: AxiosResponse<{ + data: IQueryAutocompleteResponse; + }> = await ApiV3Instance.get( + `autocomplete/attribute_keys?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&aggregateAttribute=${aggregateAttribute}&searchText=${searchText}`, + ); + + return { + statusCode: 200, + error: null, + message: response.statusText, + payload: response.data.data, + }; + } catch (e) { + return ErrorResponseHandler(e as AxiosError); + } +}; diff --git a/frontend/src/constants/useQueryKeys.ts b/frontend/src/constants/useQueryKeys.ts new file mode 100644 index 0000000000..705d5ef350 --- /dev/null +++ b/frontend/src/constants/useQueryKeys.ts @@ -0,0 +1,3 @@ +export enum QueryBuilderKeys { + GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE', +} diff --git a/frontend/src/container/MetricsApplication/Tabs/util.ts b/frontend/src/container/MetricsApplication/Tabs/util.ts index aa02902597..f6e78a6d6f 100644 --- a/frontend/src/container/MetricsApplication/Tabs/util.ts +++ b/frontend/src/container/MetricsApplication/Tabs/util.ts @@ -61,7 +61,7 @@ export function onGraphClickHandler( const points = chart.getElementsAtEventForMode( event.native, 'nearest', - { intersect: true }, + { intersect: false }, true, ); const id = `${from}_button`; diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx index 6657f030b1..806ee579e9 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx @@ -1,10 +1,9 @@ -/* eslint-disable */ -//@ts-nocheck - +/* eslint-disable @typescript-eslint/no-unused-vars */ import { Button, Tabs } from 'antd'; import TextToolTip from 'components/TextToolTip'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { timePreferance } from 'container/NewWidget/RightContainer/timeItems'; +import { QueryBuilder } from 'container/QueryBuilder'; import { cloneDeep, isEqual } from 'lodash-es'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { connect, useSelector } from 'react-redux'; @@ -31,6 +30,7 @@ import ClickHouseQueryContainer from './QueryBuilder/clickHouse'; import PromQLQueryContainer from './QueryBuilder/promQL'; import QueryBuilderQueryContainer from './QueryBuilder/queryBuilder'; import TabHeader from './TabHeader'; +import { IHandleUpdatedQuery } from './types'; import { getQueryKey } from './utils/getQueryKey'; import { showUnstagedStashConfirmBox } from './utils/userSettings'; @@ -54,9 +54,7 @@ function QuerySection({ const { search } = useLocation(); const { widgets } = selectedDashboards.data; - const urlQuery = useMemo(() => { - return new URLSearchParams(search); - }, [search]); + const urlQuery = useMemo(() => new URLSearchParams(search), [search]); const getWidget = useCallback(() => { const widgetId = urlQuery.get('widgetId'); @@ -169,6 +167,9 @@ function QuerySection({ } selectedGraph={selectedGraph} /> + + // TODO: uncomment for testing new QueryBuilder + // ), }, { diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts index eb945aa6fb..3c0ccef6e8 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts +++ b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts @@ -1,9 +1,12 @@ -import { - IBuilderFormula, - IBuilderQuery, -} from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; + +export type QueryBuilderConfig = + | { + queryVariant: 'static'; + initialDataSource: DataSource; + } + | { queryVariant: 'dropdown' }; export type QueryBuilderProps = { - queryData: IBuilderQuery[]; - queryFormula: IBuilderFormula[]; + config?: QueryBuilderConfig; }; diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/QueryBuilder/QueryBuilder.tsx index c732a27d76..e8384eb2d9 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.tsx +++ b/frontend/src/container/QueryBuilder/QueryBuilder.tsx @@ -2,14 +2,14 @@ import { useQueryBuilder } from 'hooks/useQueryBuilder'; import React from 'react'; +// ** Components +import { Query } from './components'; // ** Types import { QueryBuilderProps } from './QueryBuilder.interfaces'; -// TODO: temporary eslint disable while variable isn't used +// TODO: I think it can be components switcher, because if we have different views based on the data source, we can render based on source // eslint-disable-next-line @typescript-eslint/no-unused-vars -export function QueryBuilder(props: QueryBuilderProps): JSX.Element { - // TODO: temporary doesn't use - // eslint-disable-next-line @typescript-eslint/no-unused-vars +export function QueryBuilder({ config }: QueryBuilderProps): JSX.Element { const { queryBuilderData } = useQueryBuilder(); // Here we can use Form from antd library and fill context data or edit @@ -19,5 +19,17 @@ export function QueryBuilder(props: QueryBuilderProps): JSX.Element { // Each component can be part of antd Form list where we can add or remove items // Also need decide to make a copy of queryData for working with form or not and after it set the full new list with formulas or queries to the context // With button to add him - return
null
; + return ( +
+ {queryBuilderData.queryData.map((query, index) => ( + 1} + queryVariant={config?.queryVariant || 'dropdown'} + query={query} + /> + ))} +
+ ); } diff --git a/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.interfaces.ts b/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.interfaces.ts new file mode 100644 index 0000000000..0a34d52c6f --- /dev/null +++ b/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.interfaces.ts @@ -0,0 +1,6 @@ +import { SelectProps } from 'antd'; +import { DataSource } from 'types/common/queryBuilder'; + +export type QueryLabelProps = { + onChange: (value: DataSource) => void; +} & Omit; diff --git a/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx b/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx new file mode 100644 index 0000000000..4efe754f1a --- /dev/null +++ b/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx @@ -0,0 +1,33 @@ +import { Select } from 'antd'; +import React from 'react'; +import { DataSource } from 'types/common/queryBuilder'; +import { SelectOption } from 'types/common/select'; +// ** Helpers +import { transformToUpperCase } from 'utils/transformToUpperCase'; + +// ** Types +import { QueryLabelProps } from './DataSourceDropdown.interfaces'; + +const dataSourceMap = [DataSource.LOGS, DataSource.METRICS, DataSource.TRACES]; + +export function DataSourceDropdown(props: QueryLabelProps): JSX.Element { + const { onChange, value, style } = props; + + const dataSourceOptions: SelectOption< + DataSource, + string + >[] = dataSourceMap.map((source) => ({ + label: transformToUpperCase(source), + value: source, + })); + + return ( + - ); -} diff --git a/frontend/src/container/QueryBuilder/components/QueryLabel/index.ts b/frontend/src/container/QueryBuilder/components/QueryLabel/index.ts deleted file mode 100644 index 7403bb9ce5..0000000000 --- a/frontend/src/container/QueryBuilder/components/QueryLabel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { QueryLabel } from './QueryLabel'; diff --git a/frontend/src/container/QueryBuilder/components/index.ts b/frontend/src/container/QueryBuilder/components/index.ts index a5ced11d5b..f21c33d5c9 100644 --- a/frontend/src/container/QueryBuilder/components/index.ts +++ b/frontend/src/container/QueryBuilder/components/index.ts @@ -1,2 +1,5 @@ +export { DataSourceDropdown } from './DataSourceDropdown'; +export { FilterLabel } from './FilterLabel'; +export { Formula } from './Formula'; export { ListMarker } from './ListMarker'; -export { QueryLabel } from './QueryLabel'; +export { Query } from './Query'; diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts new file mode 100644 index 0000000000..81ba34a2c2 --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts @@ -0,0 +1,7 @@ +import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; + +export type AgregatorFilterProps = { + onChange: (value: AutocompleteData) => void; + query: IBuilderQueryForm; +}; diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx new file mode 100644 index 0000000000..13a25216ce --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx @@ -0,0 +1,84 @@ +// ** Components +import { AutoComplete, Spin } from 'antd'; +// ** Api +import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute'; +import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; +import React, { useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { SelectOption } from 'types/common/select'; +import { transformToUpperCase } from 'utils/transformToUpperCase'; + +// ** Types +import { AgregatorFilterProps } from './AggregatorFilter.intefaces'; + +export function AggregatorFilter({ + onChange, + query, +}: AgregatorFilterProps): JSX.Element { + const [searchText, setSearchText] = useState(''); + + const { data, isFetching } = useQuery( + [ + 'GET_AGGREGATE_ATTRIBUTE', + searchText, + query.aggregateOperator, + query.dataSource, + ], + async () => + getAggregateAttribute({ + aggregateOperator: query.aggregateOperator, + dataSource: query.dataSource, + searchText, + }), + { enabled: !!query.aggregateOperator && !!query.dataSource }, + ); + + const handleSearchAttribute = (searchText: string): void => { + setSearchText(searchText); + }; + + const optionsData: SelectOption[] = + data?.payload?.attributeKeys?.map((item) => ({ + label: transformStringWithPrefix({ + str: item.key, + prefix: item.type || '', + condition: !item.isColumn, + }), + value: item.key, + })) || []; + + const handleChangeAttribute = (value: string): void => { + const currentAttributeObj = data?.payload?.attributeKeys?.find( + (item) => item.key === value, + ) || { key: value, type: null, dataType: null, isColumn: null }; + + onChange(currentAttributeObj); + }; + + const value = useMemo( + () => + transformStringWithPrefix({ + str: query.aggregateAttribute.key, + prefix: query.aggregateAttribute.type || '', + condition: !query.aggregateAttribute.isColumn, + }), + [query], + ); + + return ( + : null} + options={optionsData} + value={value} + onChange={handleChangeAttribute} + /> + ); +} diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/index.ts b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/index.ts new file mode 100644 index 0000000000..2ec6993746 --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/index.ts @@ -0,0 +1 @@ +export { AggregatorFilter } from './AggregatorFilter'; diff --git a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts new file mode 100644 index 0000000000..f47e813112 --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts @@ -0,0 +1,7 @@ +import { SelectProps } from 'antd'; + +export type OperatorsSelectProps = Omit & { + operators: string[]; + onChange: (value: string) => void; + value: string; +}; diff --git a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx new file mode 100644 index 0000000000..979bac1dcc --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx @@ -0,0 +1,32 @@ +import { Select } from 'antd'; +import React from 'react'; +// ** Types +import { SelectOption } from 'types/common/select'; +// ** Helpers +import { transformToUpperCase } from 'utils/transformToUpperCase'; + +import { OperatorsSelectProps } from './OperatorsSelect.interfaces'; + +export function OperatorsSelect({ + operators, + value, + onChange, + ...props +}: OperatorsSelectProps): JSX.Element { + const operatorsOptions: SelectOption[] = operators.map( + (operator) => ({ + label: transformToUpperCase(operator), + value: operator, + }), + ); + + return ( +