From 50142321f7fd7eec7ad876db444e8a7e585698a6 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 18 Jul 2023 08:55:01 +0530 Subject: [PATCH 01/13] Shifting of graph from Dashboard to Service layer (#3107) * refactor: initial setup for full view done * feat: done with shifting of chart to services * refactor: removed the dependency dashboard action * refactor: make ondelete and onclone optional in widgetheader * refactor: optimised the allowdelete, allowclone and allowEdit * fix: build pipline error * refactor: moved contant to contant.js * refactor: create a utils and types and seperated it from component * refactor: merge the latest overview from develop * refactor: review comments changes * refactor: magic string to constants * refactor: handle the isloading for topLevelOperations * refactor: apply loading check for other api's * refactor: seperated the component * refactor: removed the graphwithoutdashboard component * fix: the type of variable from dashboard * refactor: created utils and updated types * refactor: changed the name of variable and fixed typos * fix: the menu option dropdown for services widget header * chore: ts config is updated for the isTwidgetoptions * chore: removed the unwanted file * fix: css on hover of widget header and default value * refactor: renamed the file to index in fullView * refactor: disable the edit delete clone option * fix: typos * chore: types are updated in metrics application * chore: type is updated * fix: build is fixed * refactor: changes the yaxisunit to ns of serviceoverview --------- Co-authored-by: Palash Gupta Co-authored-by: Vishal Sharma --- .../Graph/FullView/index.metricsBuilder.tsx | 137 -------- .../GridGraphLayout/Graph/FullView/index.tsx | 133 +++----- .../container/GridGraphLayout/Graph/index.tsx | 41 ++- .../GridGraphLayout/WidgetHeader/contants.ts | 13 + .../GridGraphLayout/WidgetHeader/index.tsx | 84 +++-- .../GridGraphLayout/WidgetHeader/styles.ts | 2 + .../GridGraphLayout/WidgetHeader/types.ts | 25 ++ .../GridGraphLayout/WidgetHeader/utils.ts | 24 ++ .../MetricsApplication.factory.ts | 7 +- .../MetricsPageQueries/DBCallQueries.ts | 97 +++--- .../MetricsPageQueries/ExternalQueries.ts | 155 +++++---- .../MetricsPageQueriesFactory.ts | 65 ++-- .../MetricsPageQueries/OverviewQueries.ts | 237 +++++++++---- .../MetricsApplication/Tabs/DBCall.tsx | 70 ++-- .../MetricsApplication/Tabs/External.tsx | 135 ++++---- .../MetricsApplication/Tabs/Overview.tsx | 319 +++++------------- .../Tabs/Overview/ServiceOverview.tsx | 84 +++++ .../Tabs/Overview/TopLevelOperations.tsx | 65 ++++ .../Tabs/Overview/TopOperation.tsx | 46 +++ .../MetricsApplication/Tabs/constant.ts | 3 - .../MetricsApplication/Tabs/types.ts | 3 + .../container/MetricsApplication/constant.ts | 53 +++ .../src/utils/dashboard/selectedDashboard.ts | 18 + 23 files changed, 1009 insertions(+), 807 deletions(-) delete mode 100644 frontend/src/container/GridGraphLayout/Graph/FullView/index.metricsBuilder.tsx create mode 100644 frontend/src/container/GridGraphLayout/WidgetHeader/contants.ts create mode 100644 frontend/src/container/GridGraphLayout/WidgetHeader/types.ts create mode 100644 frontend/src/container/GridGraphLayout/WidgetHeader/utils.ts create mode 100644 frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx create mode 100644 frontend/src/container/MetricsApplication/Tabs/Overview/TopLevelOperations.tsx create mode 100644 frontend/src/container/MetricsApplication/Tabs/Overview/TopOperation.tsx delete mode 100644 frontend/src/container/MetricsApplication/Tabs/constant.ts create mode 100644 frontend/src/container/MetricsApplication/Tabs/types.ts create mode 100644 frontend/src/container/MetricsApplication/constant.ts create mode 100644 frontend/src/utils/dashboard/selectedDashboard.ts diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.metricsBuilder.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.metricsBuilder.tsx deleted file mode 100644 index 3208517b86..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.metricsBuilder.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { Button } from 'antd'; -import { GraphOnClickHandler } from 'components/Graph'; -import Spinner from 'components/Spinner'; -import TimePreference from 'components/TimePreferenceDropDown'; -import GridGraphComponent from 'container/GridGraphComponent'; -import { - timeItems, - timePreferance, -} from 'container/NewWidget/RightContainer/timeItems'; -import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; -import { useStepInterval } from 'hooks/queryBuilder/useStepInterval'; -import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; -import getChartData from 'lib/getChartData'; -import { useCallback, useMemo, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { AppState } from 'store/reducers'; -import { Widgets } from 'types/api/dashboard/getAll'; -import { GlobalReducer } from 'types/reducer/globalTime'; - -import { TimeContainer } from './styles'; - -function FullView({ - widget, - fullViewOptions = true, - onClickHandler, - name, - yAxisUnit, - onDragSelect, - isDependedDataLoaded = false, -}: FullViewProps): JSX.Element { - const { selectedTime: globalSelectedTime } = useSelector< - AppState, - GlobalReducer - >((state) => state.globalTime); - - const getSelectedTime = useCallback( - () => - timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')), - [widget], - ); - - const [selectedTime, setSelectedTime] = useState({ - name: getSelectedTime()?.name || '', - enum: widget?.timePreferance || 'GLOBAL_TIME', - }); - - const queryKey = useMemo( - () => - `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`, - [selectedTime, globalSelectedTime, widget], - ); - - const updatedQuery = useStepInterval(widget?.query); - - const response = useGetQueryRange( - { - selectedTime: selectedTime.enum, - graphType: widget.panelTypes, - query: updatedQuery, - globalSelectedInterval: globalSelectedTime, - variables: getDashboardVariables(), - }, - { - queryKey, - enabled: !isDependedDataLoaded, - }, - ); - - const chartDataSet = useMemo( - () => - getChartData({ - queryData: [ - { - queryData: response?.data?.payload?.data?.result || [], - }, - ], - }), - [response], - ); - - if (response.status === 'idle' || response.status === 'loading') { - return ; - } - - return ( - <> - {fullViewOptions && ( - - - - - )} - - - - ); -} - -interface FullViewProps { - widget: Widgets; - fullViewOptions?: boolean; - onClickHandler?: GraphOnClickHandler; - name: string; - yAxisUnit?: string; - onDragSelect?: (start: number, end: number) => void; - isDependedDataLoaded?: boolean; -} - -FullView.defaultProps = { - fullViewOptions: undefined, - onClickHandler: undefined, - yAxisUnit: undefined, - onDragSelect: undefined, - isDependedDataLoaded: undefined, -}; - -export default FullView; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index 0aefcc109f..3208517b86 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -1,5 +1,4 @@ -import { Button, Typography } from 'antd'; -import getQueryResult from 'api/widgets/getQuery'; +import { Button } from 'antd'; import { GraphOnClickHandler } from 'components/Graph'; import Spinner from 'components/Spinner'; import TimePreference from 'components/TimePreferenceDropDown'; @@ -7,22 +6,18 @@ import GridGraphComponent from 'container/GridGraphComponent'; import { timeItems, timePreferance, - timePreferenceType, } from 'container/NewWidget/RightContainer/timeItems'; -import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +import { useStepInterval } from 'hooks/queryBuilder/useStepInterval'; +import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; import getChartData from 'lib/getChartData'; -import GetMaxMinTime from 'lib/getMaxMinTime'; -import GetMinMax from 'lib/getMinMax'; -import getStartAndEndTime from 'lib/getStartAndEndTime'; -import getStep from 'lib/getStep'; import { useCallback, useMemo, useState } from 'react'; -import { useQueries } from 'react-query'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; -import { PromQLWidgets } from 'types/api/dashboard/getAll'; +import { Widgets } from 'types/api/dashboard/getAll'; import { GlobalReducer } from 'types/reducer/globalTime'; -import { NotFoundContainer, TimeContainer } from './styles'; +import { TimeContainer } from './styles'; function FullView({ widget, @@ -31,8 +26,9 @@ function FullView({ name, yAxisUnit, onDragSelect, + isDependedDataLoaded = false, }: FullViewProps): JSX.Element { - const { minTime, maxTime, selectedTime: globalSelectedTime } = useSelector< + const { selectedTime: globalSelectedTime } = useSelector< AppState, GlobalReducer >((state) => state.globalTime); @@ -48,110 +44,55 @@ function FullView({ enum: widget?.timePreferance || 'GLOBAL_TIME', }); - const maxMinTime = GetMaxMinTime({ - graphType: widget.panelTypes, - maxTime, - minTime, - }); - - const getMinMax = ( - time: timePreferenceType, - ): { min: string | number; max: string | number } => { - if (time === 'GLOBAL_TIME') { - const minMax = GetMinMax(globalSelectedTime, [ - minTime / 1000000, - maxTime / 1000000, - ]); - return { - min: convertToNanoSecondsToSecond(minMax.minTime / 1000), - max: convertToNanoSecondsToSecond(minMax.maxTime / 1000), - }; - } - - const minMax = getStartAndEndTime({ - type: selectedTime.enum, - maxTime: maxMinTime.maxTime, - minTime: maxMinTime.minTime, - }); - return { min: parseInt(minMax.start, 10), max: parseInt(minMax.end, 10) }; - }; - - const queryMinMax = getMinMax(selectedTime.enum); - - const queryLength = widget.query.filter((e) => e.query.length !== 0); - - const response = useQueries( - queryLength.map((query) => ({ - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - queryFn: () => - getQueryResult({ - end: queryMinMax.max.toString(), - query: query.query, - start: queryMinMax.min.toString(), - step: `${getStep({ - start: queryMinMax.min, - end: queryMinMax.max, - inputFormat: 's', - })}`, - }), - queryHash: `${query.query}-${query.legend}-${selectedTime.enum}`, - retryOnMount: false, - })), + const queryKey = useMemo( + () => + `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`, + [selectedTime, globalSelectedTime, widget], ); - const isError = - response.find((e) => e?.data?.statusCode !== 200) !== undefined || - response.some((e) => e.isError === true); + const updatedQuery = useStepInterval(widget?.query); - const isLoading = response.some((e) => e.isLoading === true); - - const errorMessage = response.find((e) => e.data?.error !== null)?.data?.error; - - const data = response.map((responseOfQuery) => - responseOfQuery?.data?.payload?.result.map((e, index) => ({ - query: queryLength[index]?.query, - queryData: e, - legend: queryLength[index]?.legend, - })), + const response = useGetQueryRange( + { + selectedTime: selectedTime.enum, + graphType: widget.panelTypes, + query: updatedQuery, + globalSelectedInterval: globalSelectedTime, + variables: getDashboardVariables(), + }, + { + queryKey, + enabled: !isDependedDataLoaded, + }, ); const chartDataSet = useMemo( () => getChartData({ - queryData: data.map((e) => ({ - query: e?.map((e) => e.query).join(' ') || '', - queryData: e?.map((e) => e.queryData) || [], - legend: e?.map((e) => e.legend).join('') || '', - })), + queryData: [ + { + queryData: response?.data?.payload?.data?.result || [], + }, + ], }), - [data], + [response], ); - if (isLoading) { + if (response.status === 'idle' || response.status === 'loading') { return ; } - if (isError || data === undefined || data[0] === undefined) { - return ( - - {errorMessage} - - ); - } - return ( <> {fullViewOptions && ( - 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..542449ba97 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) || [], @@ -146,37 +99,43 @@ function Application(): JSX.Element { const operationPerSecWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: operationPerSec({ - servicename, - tagFilterItems, - topLevelOperations: topLevelOperations - ? topLevelOperations[servicename || ''] - : [], - }), - clickhouse_sql: [], - id: uuid(), - }), + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: operationPerSec({ + servicename, + tagFilterItems, + topLevelOperations: topLevelOperations + ? topLevelOperations[servicename || ''] + : [], + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.RATE_PER_OPS, + ), [servicename, topLevelOperations, tagFilterItems], ); const errorPercentageWidget = useMemo( () => - getWidgetQueryBuilder({ - queryType: EQueryType.QUERY_BUILDER, - promql: [], - builder: errorPercentage({ - servicename, - tagFilterItems, - topLevelOperations: topLevelOperations - ? topLevelOperations[servicename || ''] - : [], - }), - clickhouse_sql: [], - id: uuid(), - }), + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: errorPercentage({ + servicename, + tagFilterItems, + topLevelOperations: topLevelOperations + ? topLevelOperations[servicename || ''] + : [], + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.ERROR_PERCENTAGE, + ), [servicename, topLevelOperations, tagFilterItems], ); @@ -212,107 +171,17 @@ 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 +197,17 @@ function Application(): JSX.Element { > View Traces - - {topLevelOperationsIsError ? ( - - {axios.isAxiosError(topLevelOperationsError) - ? topLevelOperationsError.response?.data - : SOMETHING_WENT_WRONG} - - ) : ( - <> - Rate (ops/s) - - - - - )} - + @@ -367,43 +223,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..342ddc191d --- /dev/null +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx @@ -0,0 +1,84 @@ +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 { 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, +}: ServiceOverviewProps): JSX.Element { + const { servicename } = useParams(); + + const latencyWidget = useMemo( + () => + getWidgetQueryBuilder( + { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: latency({ + servicename, + tagFilterItems, + }), + clickhouse_sql: [], + id: uuid(), + }, + GraphTitle.LATENCY, + ), + [servicename, tagFilterItems], + ); + + return ( + <> + + + + + + + + ); +} + +interface ServiceOverviewProps { + selectedTimeStamp: number; + selectedTraceTags: string; + onDragSelect: (start: number, end: number) => void; + handleGraphClick: (type: string) => ClickHandlerType; + tagFilterItems: TagFilterItem[]; +} + +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..6ceead671d --- /dev/null +++ b/frontend/src/container/MetricsApplication/constant.ts @@ -0,0 +1,53 @@ +export const legend = { + address: '{{address}}', +}; + +export const QUERYNAME_AND_EXPRESSION = ['A', 'B', 'C']; +export const LETENCY_LEGENDS_AGGREGATEOPERATOR = ['p50', 'p90', 'p99']; +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 { + 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', +} diff --git a/frontend/src/utils/dashboard/selectedDashboard.ts b/frontend/src/utils/dashboard/selectedDashboard.ts new file mode 100644 index 0000000000..c05077f39c --- /dev/null +++ b/frontend/src/utils/dashboard/selectedDashboard.ts @@ -0,0 +1,18 @@ +import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll'; + +export const getSelectedDashboard = (dashboard: Dashboard[]): Dashboard => { + if (dashboard.length > 0) { + return dashboard[0]; + } + return {} as Dashboard; +}; + +export const getSelectedDashboardVariable = ( + dashboard: Dashboard[], +): Record => { + if (dashboard.length > 0) { + const { variables } = dashboard[0].data; + return variables; + } + return {}; +}; From 49afc2549f6b99193eaa869d5728667e869f7fb6 Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Tue, 18 Jul 2023 11:01:51 +0530 Subject: [PATCH 02/13] fix: ordering of ts and table panel (#3163) * fix: ordering of ts and table panel * chore: refactor --- pkg/query-service/app/clickhouseReader/reader.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index c537cac8ef..2bf15b90cd 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -4161,7 +4161,7 @@ func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNam // ("order", "/fetch/{Id}") // ("order", "/order") seriesToPoints := make(map[string][]v3.Point) - + var keys []string // seriesToAttrs is a mapping of key to a map of attribute key to attribute value // for each series. This is used to populate the series' attributes // For instance, for the above example, the seriesToAttrs will be @@ -4182,12 +4182,15 @@ func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNam groupBy, groupAttributes, metricPoint := readRow(vars, columnNames) sort.Strings(groupBy) key := strings.Join(groupBy, "") + if _, exists := seriesToAttrs[key]; !exists { + keys = append(keys, key) + } seriesToAttrs[key] = groupAttributes seriesToPoints[key] = append(seriesToPoints[key], metricPoint) } var seriesList []*v3.Series - for key := range seriesToPoints { + for _, key := range keys { points := seriesToPoints[key] // find the grouping sets point for the series From b5af2383746d690b88457609132f4e062667703a Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Tue, 18 Jul 2023 12:17:00 +0530 Subject: [PATCH 03/13] fix: use GLOBAL inner join instead of regular join (#3164) --- pkg/query-service/app/traces/v3/query_builder_test.go | 2 +- pkg/query-service/constants/constants.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/query-service/app/traces/v3/query_builder_test.go b/pkg/query-service/app/traces/v3/query_builder_test.go index 7ec3fa49f7..89206b857d 100644 --- a/pkg/query-service/app/traces/v3/query_builder_test.go +++ b/pkg/query-service/app/traces/v3/query_builder_test.go @@ -1108,7 +1108,7 @@ var testBuildTracesQueryData = []struct { " name FROM signoz_traces.distributed_signoz_index_v2 WHERE parentSpanID = '' AND (timestamp >= '1680066360726210000' AND " + "timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' ORDER BY durationNano DESC LIMIT 100)" + " SELECT subQuery.serviceName, subQuery.name, count() AS span_count, subQuery.durationNano, traceID" + - " FROM signoz_traces.distributed_signoz_index_v2 INNER JOIN subQuery ON distributed_signoz_index_v2.traceID" + + " FROM signoz_traces.distributed_signoz_index_v2 GLOBAL INNER JOIN subQuery ON distributed_signoz_index_v2.traceID" + " = subQuery.traceID GROUP BY traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName " + "ORDER BY subQuery.durationNano desc;", PanelType: v3.PanelTypeTrace, diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go index b9b4393584..2b8b67510c 100644 --- a/pkg/query-service/constants/constants.go +++ b/pkg/query-service/constants/constants.go @@ -246,7 +246,7 @@ const ( TracesExplorerViewSQLSelectWithSubQuery = "WITH subQuery AS (SELECT distinct on (traceID) traceID, durationNano, " + "serviceName, name FROM %s.%s WHERE parentSpanID = '' AND %s %s ORDER BY durationNano DESC " TracesExplorerViewSQLSelectQuery = "SELECT subQuery.serviceName, subQuery.name, count() AS " + - "span_count, subQuery.durationNano, traceID FROM %s.%s INNER JOIN subQuery ON %s.traceID = subQuery.traceID GROUP " + + "span_count, subQuery.durationNano, traceID FROM %s.%s GLOBAL INNER JOIN subQuery ON %s.traceID = subQuery.traceID GROUP " + "BY traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName ORDER BY subQuery.durationNano desc;" ) From b3a6deb71b78b3447af2e680ca2089f7282a5815 Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:12:05 +0300 Subject: [PATCH 04/13] fix: resets the state of adding a new panel (#3122) Co-authored-by: Vishal Sharma Co-authored-by: Palash Gupta --- frontend/src/container/GridGraphLayout/index.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/container/GridGraphLayout/index.tsx b/frontend/src/container/GridGraphLayout/index.tsx index 2235b93708..6b6ec7d883 100644 --- a/frontend/src/container/GridGraphLayout/index.tsx +++ b/frontend/src/container/GridGraphLayout/index.tsx @@ -326,6 +326,13 @@ function GridGraph(props: Props): JSX.Element { errorMessage, ]); + useEffect( + () => (): void => { + toggleAddWidget(false); + }, + [toggleAddWidget], + ); + return ( Date: Tue, 18 Jul 2023 13:07:27 +0300 Subject: [PATCH 05/13] feat: legend show be hidden for the graph with no data (#3168) Co-authored-by: gitstart Co-authored-by: Nitesh Singh Co-authored-by: RubensRafael Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com> --- frontend/src/components/Graph/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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, From 07833b98592c37790e40700402dbf8ac3438e7b9 Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Tue, 18 Jul 2023 13:17:32 +0300 Subject: [PATCH 06/13] feat: add the table view for the traces explorer (#2964) * feat: add dynamic table based on query * feat: add the list view for the traces explorer * feat: add the list view for the traces explorer * feat: add the list view for the traces explorer * feat: add the table view for the traces explorer * feat: remove unnecessary part of code for the table view in the traces explorer * fix: resolve comments --------- Co-authored-by: Yevhen Shevchenko Co-authored-by: Nazarenko19 Co-authored-by: Vishal Sharma Co-authored-by: Palash Gupta --- .../TracesExplorer/TableView/index.tsx | 53 +++++++++++++++++++ frontend/src/pages/TracesExplorer/utils.tsx | 6 +++ frontend/src/types/api/widgets/getQuery.ts | 5 +- 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 frontend/src/container/TracesExplorer/TableView/index.tsx diff --git a/frontend/src/container/TracesExplorer/TableView/index.tsx b/frontend/src/container/TracesExplorer/TableView/index.tsx new file mode 100644 index 0000000000..11f27f6201 --- /dev/null +++ b/frontend/src/container/TracesExplorer/TableView/index.tsx @@ -0,0 +1,53 @@ +import { Space } from 'antd'; +import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { QueryTable } from 'container/QueryTable'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { memo } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +function TableView(): JSX.Element { + const { stagedQuery, panelType } = useQueryBuilder(); + + const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const { data, isLoading } = useGetQueryRange( + { + query: stagedQuery || initialQueriesMap.traces, + graphType: panelType || PANEL_TYPES.TABLE, + selectedTime: 'GLOBAL_TIME', + globalSelectedInterval: globalSelectedTime, + params: { + dataSource: 'traces', + }, + }, + { + queryKey: [ + REACT_QUERY_KEY.GET_QUERY_RANGE, + globalSelectedTime, + maxTime, + minTime, + stagedQuery, + ], + enabled: !!stagedQuery && panelType === PANEL_TYPES.TABLE, + }, + ); + + return ( + + + + ); +} + +export default memo(TableView); diff --git a/frontend/src/pages/TracesExplorer/utils.tsx b/frontend/src/pages/TracesExplorer/utils.tsx index c2aa7b58ce..dc3f1197b3 100644 --- a/frontend/src/pages/TracesExplorer/utils.tsx +++ b/frontend/src/pages/TracesExplorer/utils.tsx @@ -3,6 +3,7 @@ import TabLabel from 'components/TabLabel'; import { PANEL_TYPES } from 'constants/queryBuilder'; import TimeSeriesView from 'container/TimeSeriesView'; import ListView from 'container/TracesExplorer/ListView'; +import TableView from 'container/TracesExplorer/TableView'; import TracesView from 'container/TracesExplorer/TracesView'; import { DataSource } from 'types/common/queryBuilder'; @@ -42,4 +43,9 @@ export const getTabsItems = ({ key: PANEL_TYPES.TIME_SERIES, children: , }, + { + label: 'Table View', + key: PANEL_TYPES.TABLE, + children: , + }, ]; diff --git a/frontend/src/types/api/widgets/getQuery.ts b/frontend/src/types/api/widgets/getQuery.ts index f60eebc0bb..0b36af1541 100644 --- a/frontend/src/types/api/widgets/getQuery.ts +++ b/frontend/src/types/api/widgets/getQuery.ts @@ -5,10 +5,7 @@ export interface PayloadProps { result: QueryData[]; } -export type ListItem = { - timestamp: string; - data: Omit; -}; +export type ListItem = { timestamp: string; data: Omit }; export interface QueryData { metric: { From 8f1451e15487343dfc7c0f0b5edb28fe9ebed366 Mon Sep 17 00:00:00 2001 From: dnazarenkoo <134951516+dnazarenkoo@users.noreply.github.com> Date: Tue, 18 Jul 2023 14:48:34 +0300 Subject: [PATCH 07/13] feat: add the ability to drag columns (#3100) * feat: add the ability to drag columns * feat: add the ability to drag columns in the logs explorer * feat: update drag logic * fix: resolve comments * feat: resolve comment regarding error handling --------- Co-authored-by: Vishal Sharma Co-authored-by: Palash Gupta --- frontend/package.json | 1 + .../components/ResizeTable/ResizeTable.tsx | 54 +++++++++---- frontend/src/components/ResizeTable/styles.ts | 10 ++- frontend/src/components/ResizeTable/types.ts | 6 ++ frontend/src/constants/localStorage.ts | 2 + .../InfinityTableView/LogsCustomTable.tsx | 24 ++++++ .../InfinityTableView/index.tsx | 60 +++++++++++---- .../InfinityTableView/styles.ts | 8 +- .../container/OptionsMenu/useOptionsMenu.ts | 2 + .../TracesExplorer/ListView/index.tsx | 20 ++++- frontend/src/hooks/useDragColumns/configs.ts | 7 ++ frontend/src/hooks/useDragColumns/index.ts | 75 +++++++++++++++++++ frontend/src/hooks/useDragColumns/types.ts | 10 +++ frontend/src/hooks/useDragColumns/utils.ts | 37 +++++++++ frontend/yarn.lock | 28 ++++++- 15 files changed, 307 insertions(+), 37 deletions(-) create mode 100644 frontend/src/components/ResizeTable/types.ts create mode 100644 frontend/src/container/LogsExplorerList/InfinityTableView/LogsCustomTable.tsx create mode 100644 frontend/src/hooks/useDragColumns/configs.ts create mode 100644 frontend/src/hooks/useDragColumns/index.ts create mode 100644 frontend/src/hooks/useDragColumns/types.ts create mode 100644 frontend/src/hooks/useDragColumns/utils.ts 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/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/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/LogsExplorerList/InfinityTableView/LogsCustomTable.tsx b/frontend/src/container/LogsExplorerList/InfinityTableView/LogsCustomTable.tsx new file mode 100644 index 0000000000..d7ba10fb01 --- /dev/null +++ b/frontend/src/container/LogsExplorerList/InfinityTableView/LogsCustomTable.tsx @@ -0,0 +1,24 @@ +import { dragColumnParams } from 'hooks/useDragColumns/configs'; +import ReactDragListView from 'react-drag-listview'; +import { TableComponents } from 'react-virtuoso'; + +import { TableStyled } from './styles'; + +interface LogsCustomTableProps { + handleDragEnd: (fromIndex: number, toIndex: number) => void; +} + +export const LogsCustomTable = ({ + handleDragEnd, +}: LogsCustomTableProps): TableComponents['Table'] => + function CustomTable({ style, children }): JSX.Element { + return ( + + {children} + + ); + }; diff --git a/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx b/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx index d6401c8485..848cd2512c 100644 --- a/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx +++ b/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx @@ -1,22 +1,26 @@ import { ColumnTypeRender } from 'components/Logs/TableView/types'; import { useTableView } from 'components/Logs/TableView/useTableView'; -import { cloneElement, ReactElement, ReactNode, useCallback } from 'react'; +import { LOCALSTORAGE } from 'constants/localStorage'; +import useDragColumns from 'hooks/useDragColumns'; +import { getDraggedColumns } from 'hooks/useDragColumns/utils'; +import { + cloneElement, + ReactElement, + ReactNode, + useCallback, + useMemo, +} from 'react'; import { TableComponents, TableVirtuoso } from 'react-virtuoso'; import { infinityDefaultStyles } from './config'; +import { LogsCustomTable } from './LogsCustomTable'; import { TableCellStyled, TableHeaderCellStyled, TableRowStyled, - TableStyled, } from './styles'; import { InfinityTableProps } from './types'; -// eslint-disable-next-line react/function-component-definition -const CustomTable: TableComponents['Table'] = ({ style, children }) => ( - {children} -); - // eslint-disable-next-line react/function-component-definition const CustomTableRow: TableComponents['TableRow'] = ({ children, @@ -31,11 +35,25 @@ function InfinityTable({ }: InfinityTableProps): JSX.Element | null { const { onEndReached } = infitiyTableProps; const { dataSource, columns } = useTableView(tableViewProps); + const { draggedColumns, onDragColumns } = useDragColumns< + Record + >(LOCALSTORAGE.LOGS_LIST_COLUMNS); + + const tableColumns = useMemo( + () => getDraggedColumns>(columns, draggedColumns), + [columns, draggedColumns], + ); + + const handleDragEnd = useCallback( + (fromIndex: number, toIndex: number) => + onDragColumns(tableColumns, fromIndex, toIndex), + [tableColumns, onDragColumns], + ); const itemContent = useCallback( (index: number, log: Record): JSX.Element => ( <> - {columns.map((column) => { + {tableColumns.map((column) => { if (!column.render) 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/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts index 96094a7995..4e2a691666 100644 --- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts +++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts @@ -30,6 +30,7 @@ interface UseOptionsMenuProps { interface UseOptionsMenu { options: OptionsQuery; config: OptionsMenuConfig; + handleOptionsChange: (newQueryData: OptionsQuery) => void; } const useOptionsMenu = ({ @@ -306,6 +307,7 @@ const useOptionsMenu = ({ return { options: optionsQueryData, config: optionsMenuConfig, + handleOptionsChange: handleRedirectWithOptionsData, }; }; 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/hooks/useDragColumns/configs.ts b/frontend/src/hooks/useDragColumns/configs.ts new file mode 100644 index 0000000000..fee591bc4e --- /dev/null +++ b/frontend/src/hooks/useDragColumns/configs.ts @@ -0,0 +1,7 @@ +export const COLUMNS = 'columns'; + +export const dragColumnParams = { + ignoreSelector: '.react-resizable-handle', + nodeSelector: 'th', + handleSelector: '.dragHandler', +}; diff --git a/frontend/src/hooks/useDragColumns/index.ts b/frontend/src/hooks/useDragColumns/index.ts new file mode 100644 index 0000000000..297fa55673 --- /dev/null +++ b/frontend/src/hooks/useDragColumns/index.ts @@ -0,0 +1,75 @@ +import { ColumnsType } from 'antd/es/table'; +import getFromLocalstorage from 'api/browser/localstorage/get'; +import setToLocalstorage from 'api/browser/localstorage/set'; +import { LOCALSTORAGE } from 'constants/localStorage'; +import useUrlQueryData from 'hooks/useUrlQueryData'; +import { useCallback, useEffect, useMemo } from 'react'; + +import { COLUMNS } from './configs'; +import { UseDragColumns } from './types'; + +const useDragColumns = (storageKey: LOCALSTORAGE): UseDragColumns => { + const { + query: draggedColumnsQuery, + queryData: draggedColumns, + redirectWithQuery: redirectWithDraggedColumns, + } = useUrlQueryData>(COLUMNS, []); + + const localStorageDraggedColumns = useMemo( + () => getFromLocalstorage(storageKey), + [storageKey], + ); + + const handleRedirectWithDraggedColumns = useCallback( + (columns: ColumnsType) => { + redirectWithDraggedColumns(columns); + + setToLocalstorage(storageKey, JSON.stringify(columns)); + }, + [storageKey, redirectWithDraggedColumns], + ); + + const onDragColumns = useCallback( + (columns: ColumnsType, fromIndex: number, toIndex: number): void => { + const columnsData = [...columns]; + const item = columnsData.splice(fromIndex, 1)[0]; + columnsData.splice(toIndex, 0, item); + + handleRedirectWithDraggedColumns(columnsData); + }, + [handleRedirectWithDraggedColumns], + ); + + const redirectWithNewDraggedColumns = useCallback( + async (localStorageColumns: string) => { + let nextDraggedColumns: ColumnsType = []; + + try { + const parsedDraggedColumns = await JSON.parse(localStorageColumns); + nextDraggedColumns = parsedDraggedColumns; + } catch (e) { + console.log('error while parsing json'); + } finally { + redirectWithDraggedColumns(nextDraggedColumns); + } + }, + [redirectWithDraggedColumns], + ); + + useEffect(() => { + if (draggedColumnsQuery || !localStorageDraggedColumns) return; + + redirectWithNewDraggedColumns(localStorageDraggedColumns); + }, [ + draggedColumnsQuery, + localStorageDraggedColumns, + redirectWithNewDraggedColumns, + ]); + + return { + draggedColumns, + onDragColumns, + }; +}; + +export default useDragColumns; diff --git a/frontend/src/hooks/useDragColumns/types.ts b/frontend/src/hooks/useDragColumns/types.ts new file mode 100644 index 0000000000..268edbb910 --- /dev/null +++ b/frontend/src/hooks/useDragColumns/types.ts @@ -0,0 +1,10 @@ +import { ColumnsType } from 'antd/es/table'; + +export type UseDragColumns = { + draggedColumns: ColumnsType; + onDragColumns: ( + columns: ColumnsType, + fromIndex: number, + toIndex: number, + ) => void; +}; diff --git a/frontend/src/hooks/useDragColumns/utils.ts b/frontend/src/hooks/useDragColumns/utils.ts new file mode 100644 index 0000000000..df11981066 --- /dev/null +++ b/frontend/src/hooks/useDragColumns/utils.ts @@ -0,0 +1,37 @@ +import { ColumnsType } from 'antd/es/table'; + +const filterColumns = ( + initialColumns: ColumnsType, + findColumns: ColumnsType, + isColumnExist = true, +): ColumnsType => + initialColumns.filter(({ title: columnTitle }) => { + const column = findColumns.find(({ title }) => title === columnTitle); + + return isColumnExist ? !!column : !column; + }); + +export const getDraggedColumns = ( + currentColumns: ColumnsType, + draggedColumns: ColumnsType, +): ColumnsType => { + if (draggedColumns.length) { + const actualDruggedColumns = filterColumns(draggedColumns, currentColumns); + const newColumns = filterColumns( + currentColumns, + actualDruggedColumns, + false, + ); + + return [...actualDruggedColumns, ...newColumns].reduce((acc, { title }) => { + const column = currentColumns.find( + ({ title: columnTitle }) => title === columnTitle, + ); + + if (column) return [...acc, column]; + return acc; + }, [] as ColumnsType); + } + + return currentColumns; +}; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index b72d78ce59..de53c86183 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3719,6 +3719,14 @@ babel-preset-react-app@^10.0.0: babel-plugin-macros "^3.1.0" babel-plugin-transform-react-remove-prop-types "^0.4.24" +babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" @@ -4474,6 +4482,11 @@ core-js-compat@^3.25.1: dependencies: browserslist "^4.21.5" +core-js@^2.4.0: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" @@ -9999,7 +10012,7 @@ prompts@^2.0.1, prompts@^2.4.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@15, prop-types@15.x, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@15, prop-types@15.x, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -10513,6 +10526,14 @@ react-dom@18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-drag-listview@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-drag-listview/-/react-drag-listview-2.0.0.tgz#b8e7ec5f980ecbbf3abb85f50db0b03cd764edbf" + integrity sha512-7Apx/1Xt4qu+JHHP0rH6aLgZgS7c2MX8ocHVGCi03KfeIWEu0t14MhT3boQKM33l5eJrE/IWfExFTvoYq22fsg== + dependencies: + babel-runtime "^6.26.0" + prop-types "^15.5.8" + react-draggable@^4.0.0, react-draggable@^4.0.3: version "4.4.5" resolved "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz" @@ -10808,6 +10829,11 @@ regenerator-runtime@0.13.9: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + regenerator-runtime@^0.13.11: version "0.13.11" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" From 7822b4efee7a231c656af7f5fa84a2d70638b354 Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Tue, 18 Jul 2023 18:12:56 +0530 Subject: [PATCH 08/13] fix: encode email in loginPrecheck API (#3171) --- frontend/src/api/user/loginPrecheck.ts | 6 +++--- frontend/src/types/api/user/loginPrecheck.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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/types/api/user/loginPrecheck.ts b/frontend/src/types/api/user/loginPrecheck.ts index d2bd8772db..fed34eacec 100644 --- a/frontend/src/types/api/user/loginPrecheck.ts +++ b/frontend/src/types/api/user/loginPrecheck.ts @@ -6,6 +6,6 @@ export interface PayloadProps { } export interface Props { - email?: string; + email: string; path?: string; } From 5a2a987a9b2b655f67975115bea2a5b4ca9a03f8 Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Wed, 19 Jul 2023 09:54:27 +0530 Subject: [PATCH 09/13] feat: enable limit on ts (traces) (#3157) * feat: enable limit on ts --- pkg/query-service/app/querier/querier.go | 2 +- .../app/queryBuilder/query_builder.go | 24 +- .../app/traces/v3/query_builder.go | 157 ++++----- .../app/traces/v3/query_builder_test.go | 298 +++++++++++++++--- 4 files changed, 365 insertions(+), 116 deletions(-) diff --git a/pkg/query-service/app/querier/querier.go b/pkg/query-service/app/querier/querier.go index b18e56bcb6..7284bddc9f 100644 --- a/pkg/query-service/app/querier/querier.go +++ b/pkg/query-service/app/querier/querier.go @@ -250,7 +250,7 @@ func (q *querier) runBuilderQueries(ctx context.Context, params *v3.QueryRangePa } if builderQuery.DataSource == v3.DataSourceTraces { - query, err := tracesV3.PrepareTracesQuery(params.Start, params.End, params.CompositeQuery.QueryType, params.CompositeQuery.PanelType, builderQuery, keys) + query, err := tracesV3.PrepareTracesQuery(params.Start, params.End, params.CompositeQuery.PanelType, builderQuery, keys, "") if err != nil { errQueriesByName[queryName] = err.Error() continue diff --git a/pkg/query-service/app/queryBuilder/query_builder.go b/pkg/query-service/app/queryBuilder/query_builder.go index 1d16c8d709..c3f8b0d4d0 100644 --- a/pkg/query-service/app/queryBuilder/query_builder.go +++ b/pkg/query-service/app/queryBuilder/query_builder.go @@ -39,7 +39,7 @@ var SupportedFunctions = []string{ var EvalFuncs = map[string]govaluate.ExpressionFunction{} -type prepareTracesQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery, keys map[string]v3.AttributeKey) (string, error) +type prepareTracesQueryFunc func(start, end int64, panelType v3.PanelType, bq *v3.BuilderQuery, keys map[string]v3.AttributeKey, graphLimitQtype string) (string, error) type prepareLogsQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery, graphLimitQtype string) (string, error) type prepareMetricQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery) (string, error) @@ -147,11 +147,25 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3, args ...in if len(args) > 0 { keys = args[0].(map[string]v3.AttributeKey) } - queryString, err := qb.options.BuildTraceQuery(params.Start, params.End, compositeQuery.QueryType, compositeQuery.PanelType, query, keys) - if err != nil { - return nil, err + // for ts query with group by and limit form two queries + if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 { + limitQuery, err := qb.options.BuildTraceQuery(params.Start, params.End, compositeQuery.PanelType, query, keys, constants.FirstQueryGraphLimit) + if err != nil { + return nil, err + } + placeholderQuery, err := qb.options.BuildTraceQuery(params.Start, params.End, compositeQuery.PanelType, query, keys, constants.SecondQueryGraphLimit) + if err != nil { + return nil, err + } + query := fmt.Sprintf(placeholderQuery, limitQuery) + queries[queryName] = query + } else { + queryString, err := qb.options.BuildTraceQuery(params.Start, params.End, compositeQuery.PanelType, query, keys, "") + if err != nil { + return nil, err + } + queries[queryName] = queryString } - queries[queryName] = queryString case v3.DataSourceLogs: // for ts query with limit replace it as it is already formed if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 { diff --git a/pkg/query-service/app/traces/v3/query_builder.go b/pkg/query-service/app/traces/v3/query_builder.go index e8142eeead..1f9dfefbd9 100644 --- a/pkg/query-service/app/traces/v3/query_builder.go +++ b/pkg/query-service/app/traces/v3/query_builder.go @@ -108,6 +108,18 @@ func getSelectLabels(aggregatorOperator v3.AggregateOperator, groupBy []v3.Attri return selectLabels } +func getSelectKeys(aggregatorOperator v3.AggregateOperator, groupBy []v3.AttributeKey) string { + var selectLabels []string + if aggregatorOperator == v3.AggregateOperatorNoOp { + return "" + } else { + for _, tag := range groupBy { + selectLabels = append(selectLabels, fmt.Sprintf("`%s`", tag.Key)) + } + } + return strings.Join(selectLabels, ",") +} + func getSelectColumns(sc []v3.AttributeKey, keys map[string]v3.AttributeKey) string { var columns []string for _, tag := range sc { @@ -219,7 +231,7 @@ func handleEmptyValuesInGroupBy(keys map[string]v3.AttributeKey, groupBy []v3.At return "", nil } -func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName string, keys map[string]v3.AttributeKey, panelType v3.PanelType) (string, error) { +func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName string, keys map[string]v3.AttributeKey, panelType v3.PanelType, graphLimitQtype string) (string, error) { filterSubQuery, err := buildTracesFilterQuery(mq.Filters, keys) if err != nil { @@ -236,24 +248,27 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str } var queryTmpl string - - if panelType == v3.PanelTypeTable { + if graphLimitQtype == constants.FirstQueryGraphLimit { + queryTmpl = "SELECT" + } else if panelType == v3.PanelTypeTable { queryTmpl = - "SELECT now() as ts," + selectLabels + - " %s as value " + - "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_TABLENAME + - " where " + spanIndexTableTimeFilter + "%s" + - "%s%s" + - "%s" + "SELECT now() as ts," } else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { // Select the aggregate value for interval queryTmpl = - fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d SECOND) AS ts,", step) + selectLabels + - " %s as value " + - "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_TABLENAME + - " where " + spanIndexTableTimeFilter + "%s" + - "%s%s" + - "%s" + fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d SECOND) AS ts,", step) + } + + queryTmpl = queryTmpl + selectLabels + + " %s as value " + + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_TABLENAME + + " where " + spanIndexTableTimeFilter + "%s" + + "%s%s" + + "%s" + + // we don't need value for first query + if graphLimitQtype == constants.FirstQueryGraphLimit { + queryTmpl = "SELECT " + getSelectKeys(mq.AggregateOperator, mq.GroupBy) + " from (" + queryTmpl + ")" } emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(keys, mq.GroupBy) @@ -262,7 +277,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str } filterSubQuery += emptyValuesInGroupByFilter - groupBy := groupByAttributeKeyTags(panelType, mq.GroupBy...) + groupBy := groupByAttributeKeyTags(panelType, graphLimitQtype, mq.GroupBy...) if groupBy != "" { groupBy = " group by " + groupBy } @@ -271,6 +286,11 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str if orderBy != "" { orderBy = " order by " + orderBy } + + if graphLimitQtype == constants.SecondQueryGraphLimit { + filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", getSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "%s)" + } + aggregationKey := "" if mq.AggregateAttribute.Key != "" { aggregationKey = getColumnName(mq.AggregateAttribute, keys) @@ -326,7 +346,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str var query string if panelType == v3.PanelTypeTrace { withSubQuery := fmt.Sprintf(constants.TracesExplorerViewSQLSelectWithSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME, spanIndexTableTimeFilter, filterSubQuery) - withSubQuery = addLimitToQuery(withSubQuery, mq.Limit, panelType) + withSubQuery = addLimitToQuery(withSubQuery, mq.Limit) if mq.Offset != 0 { withSubQuery = addOffsetToQuery(withSubQuery, mq.Offset) } @@ -367,84 +387,60 @@ func enrichOrderBy(items []v3.OrderBy, keys map[string]v3.AttributeKey) []v3.Ord // groupBy returns a string of comma separated tags for group by clause // `ts` is always added to the group by clause -func groupBy(panelType v3.PanelType, tags ...string) string { - if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { +func groupBy(panelType v3.PanelType, graphLimitQtype string, tags ...string) string { + if (graphLimitQtype != constants.FirstQueryGraphLimit) && (panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue) { tags = append(tags, "ts") } return strings.Join(tags, ",") } -func groupByAttributeKeyTags(panelType v3.PanelType, tags ...v3.AttributeKey) string { +func groupByAttributeKeyTags(panelType v3.PanelType, graphLimitQtype string, tags ...v3.AttributeKey) string { groupTags := []string{} for _, tag := range tags { groupTags = append(groupTags, fmt.Sprintf("`%s`", tag.Key)) } - return groupBy(panelType, groupTags...) + return groupBy(panelType, graphLimitQtype, groupTags...) } // orderBy returns a string of comma separated tags for order by clause // if there are remaining items which are not present in tags they are also added // if the order is not specified, it defaults to ASC -func orderBy(panelType v3.PanelType, items []v3.OrderBy, tags []string, keys map[string]v3.AttributeKey) []string { +func orderBy(panelType v3.PanelType, items []v3.OrderBy, tagLookup map[string]struct{}, keys map[string]v3.AttributeKey) []string { var orderBy []string - // create a lookup - addedToOrderBy := map[string]bool{} - itemsLookup := map[string]v3.OrderBy{} - - for i := 0; i < len(items); i++ { - addedToOrderBy[items[i].ColumnName] = false - itemsLookup[items[i].ColumnName] = items[i] - } - - for _, tag := range tags { - if item, ok := itemsLookup[tag]; ok { - orderBy = append(orderBy, fmt.Sprintf("`%s` %s", item.ColumnName, item.Order)) - addedToOrderBy[item.ColumnName] = true - } else { - orderBy = append(orderBy, fmt.Sprintf("`%s` ASC", tag)) - } - } - - // users might want to order by value of aggregation for _, item := range items { if item.ColumnName == constants.SigNozOrderByValue { orderBy = append(orderBy, fmt.Sprintf("value %s", item.Order)) - addedToOrderBy[item.ColumnName] = true - } - } - - // add the remaining items - if panelType == v3.PanelTypeList { - for _, item := range items { - // since these are not present in tags we will have to select them correctly - // for list view there is no need to check if it was added since they wont be added yet but this is just for safety - if !addedToOrderBy[item.ColumnName] { - attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn} - name := getColumnName(attr, keys) - - if item.IsColumn { - orderBy = append(orderBy, fmt.Sprintf("`%s` %s", name, item.Order)) - } else { - orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order)) - } + } else if _, ok := tagLookup[item.ColumnName]; ok { + orderBy = append(orderBy, fmt.Sprintf("`%s` %s", item.ColumnName, item.Order)) + } else if panelType == v3.PanelTypeList { + attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn} + name := getColumnName(attr, keys) + if item.IsColumn { + orderBy = append(orderBy, fmt.Sprintf("`%s` %s", name, item.Order)) + } else { + orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order)) } } } + return orderBy } func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []v3.AttributeKey, keys map[string]v3.AttributeKey) string { - var groupTags []string - for _, tag := range tags { - groupTags = append(groupTags, tag.Key) + tagLookup := map[string]struct{}{} + for _, v := range tags { + tagLookup[v.Key] = struct{}{} } - orderByArray := orderBy(panelType, items, groupTags, keys) - if panelType == v3.PanelTypeList && len(orderByArray) == 0 { - orderByArray = append(orderByArray, constants.TIMESTAMP+" DESC") - } else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { - orderByArray = append(orderByArray, "ts") + orderByArray := orderBy(panelType, items, tagLookup, keys) + + if len(orderByArray) == 0 { + if panelType == v3.PanelTypeList { + orderByArray = append(orderByArray, constants.TIMESTAMP+" DESC") + } else if panelType == v3.PanelTypeGraph { + orderByArray = append(orderByArray, "value DESC") + } } str := strings.Join(orderByArray, ",") @@ -480,7 +476,7 @@ func reduceToQuery(query string, reduceTo v3.ReduceToOperator, aggregateOperator return query, nil } -func addLimitToQuery(query string, limit uint64, panelType v3.PanelType) string { +func addLimitToQuery(query string, limit uint64) string { if limit == 0 { limit = 100 } @@ -491,16 +487,33 @@ func addOffsetToQuery(query string, offset uint64) string { return fmt.Sprintf("%s OFFSET %d", query, offset) } -func PrepareTracesQuery(start, end int64, queryType v3.QueryType, panelType v3.PanelType, mq *v3.BuilderQuery, keys map[string]v3.AttributeKey) (string, error) { - query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys, panelType) +func PrepareTracesQuery(start, end int64, panelType v3.PanelType, mq *v3.BuilderQuery, keys map[string]v3.AttributeKey, graphLimitQtype string) (string, error) { + if graphLimitQtype == constants.FirstQueryGraphLimit { + // give me just the group by names + query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys, panelType, graphLimitQtype) + if err != nil { + return "", err + } + query = addLimitToQuery(query, mq.Limit) + + return query, nil + } else if graphLimitQtype == constants.SecondQueryGraphLimit { + query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys, panelType, graphLimitQtype) + if err != nil { + return "", err + } + return query, nil + } + + query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys, panelType, graphLimitQtype) if err != nil { return "", err } if panelType == v3.PanelTypeValue { query, err = reduceToQuery(query, mq.ReduceTo, mq.AggregateOperator) } - if panelType == v3.PanelTypeList { - query = addLimitToQuery(query, mq.Limit, panelType) + if panelType == v3.PanelTypeList || panelType == v3.PanelTypeTable { + query = addLimitToQuery(query, mq.Limit) if mq.Offset != 0 { query = addOffsetToQuery(query, mq.Offset) diff --git a/pkg/query-service/app/traces/v3/query_builder_test.go b/pkg/query-service/app/traces/v3/query_builder_test.go index 89206b857d..c7abe75492 100644 --- a/pkg/query-service/app/traces/v3/query_builder_test.go +++ b/pkg/query-service/app/traces/v3/query_builder_test.go @@ -323,8 +323,8 @@ var testOrderBy = []struct { Name string PanelType v3.PanelType Items []v3.OrderBy - Tags []string - Result []string + Tags []v3.AttributeKey + Result string }{ { Name: "Test 1", @@ -339,8 +339,10 @@ var testOrderBy = []struct { Order: "desc", }, }, - Tags: []string{"name"}, - Result: []string{"`name` asc", "value desc"}, + Tags: []v3.AttributeKey{ + {Key: "name"}, + }, + Result: "`name` asc,value desc", }, { Name: "Test 2", @@ -355,8 +357,11 @@ var testOrderBy = []struct { Order: "asc", }, }, - Tags: []string{"name", "bytes"}, - Result: []string{"`name` asc", "`bytes` asc"}, + Tags: []v3.AttributeKey{ + {Key: "name"}, + {Key: "bytes"}, + }, + Result: "`name` asc,`bytes` asc", }, { Name: "Test 3", @@ -375,8 +380,11 @@ var testOrderBy = []struct { Order: "asc", }, }, - Tags: []string{"name", "bytes"}, - Result: []string{"`name` asc", "`bytes` asc", "value asc"}, + Tags: []v3.AttributeKey{ + {Key: "name"}, + {Key: "bytes"}, + }, + Result: "`name` asc,value asc,`bytes` asc", }, { Name: "Test 4", @@ -398,8 +406,11 @@ var testOrderBy = []struct { DataType: v3.AttributeKeyDataTypeString, }, }, - Tags: []string{"name", "bytes"}, - Result: []string{"`name` asc", "`bytes` asc", "stringTagMap['response_time'] desc"}, + Tags: []v3.AttributeKey{ + {Key: "name"}, + {Key: "bytes"}, + }, + Result: "`name` asc,`bytes` asc,stringTagMap['response_time'] desc", }, { Name: "Test 5", @@ -426,15 +437,15 @@ var testOrderBy = []struct { Order: "desc", }, }, - Tags: []string{}, - Result: []string{"`name` asc", "`bytes` asc", "stringTagMap['response_time'] desc"}, + Tags: []v3.AttributeKey{}, + Result: "`name` asc,`bytes` asc,stringTagMap['response_time'] desc", }, } func TestOrderBy(t *testing.T) { for _, tt := range testOrderBy { Convey("testOrderBy", t, func() { - res := orderBy(tt.PanelType, tt.Items, tt.Tags, map[string]v3.AttributeKey{ + res := orderByAttributeKeyTags(tt.PanelType, tt.Items, tt.Tags, map[string]v3.AttributeKey{ "name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, "bytes": {Key: "bytes", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, "response_time": {Key: "response_time", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: false}, @@ -470,7 +481,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " group by ts order by ts", + " group by ts order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -486,7 +497,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, count()/60 as value from" + " signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <=" + - " '1680066458000000000') group by ts order by ts", + " '1680066458000000000') group by ts order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -505,7 +516,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts," + " toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v2" + " where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " AND stringTagMap['customer_id'] = '10001' group by ts order by ts", + " AND stringTagMap['customer_id'] = '10001' group by ts order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -522,7 +533,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " group by ts order by ts", + " group by ts order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -539,7 +550,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " AND has(stringTagMap, 'user_name') group by ts order by ts", + " AND has(stringTagMap, 'user_name') group by ts order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -556,7 +567,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " AND name != '' group by ts order by ts", + " AND name != '' group by ts order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -576,7 +587,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " AND numberTagMap['bytes'] > 100.000000 AND has(stringTagMap, 'user_name') group by ts order by ts", + " AND numberTagMap['bytes'] > 100.000000 AND has(stringTagMap, 'user_name') group by ts order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -594,7 +605,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(name))) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " group by ts order by value ASC,ts", + " group by ts order by value ASC", PanelType: v3.PanelTypeGraph, }, { @@ -611,7 +622,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name'])))" + " as value from signoz_traces.distributed_signoz_index_v2 where" + - " (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') group by ts order by ts", + " (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') group by ts order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -630,7 +641,7 @@ var testBuildTracesQueryData = []struct { }, }, GroupBy: []v3.AttributeKey{{Key: "http.method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, - OrderBy: []v3.OrderBy{{ColumnName: "http.method", Order: "ASC"}, {ColumnName: "ts", Order: "ASC"}}, + OrderBy: []v3.OrderBy{{ColumnName: "http.method", Order: "ASC"}}, }, TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts," + @@ -639,7 +650,7 @@ var testBuildTracesQueryData = []struct { "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['http.method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " + "AND has(stringTagMap, 'http.method') group by `http.method`,ts " + - "order by `http.method` ASC,ts", + "order by `http.method` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -671,7 +682,7 @@ var testBuildTracesQueryData = []struct { "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " + "AND has(stringTagMap, 'method') AND has(resourceTagsMap, 'x') group by `method`,`x`,ts " + - "order by `method` ASC,`x` ASC,ts", + "order by `method` ASC,`x` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -699,7 +710,7 @@ var testBuildTracesQueryData = []struct { "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' " + "AND has(stringTagMap, 'method') group by `method`,ts " + - "order by `method` ASC,ts", + "order by `method` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -727,7 +738,7 @@ var testBuildTracesQueryData = []struct { "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' " + "AND has(stringTagMap, 'method') group by `method`,ts " + - "order by `method` ASC,ts", + "order by `method` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -755,7 +766,7 @@ var testBuildTracesQueryData = []struct { "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' " + "AND has(stringTagMap, 'method') group by `method`,ts " + - "order by `method` ASC,ts", + "order by `method` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -783,7 +794,7 @@ var testBuildTracesQueryData = []struct { "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND stringTagMap['method'] = 'GET' " + "AND has(stringTagMap, 'method') group by `method`,ts " + - "order by `method` ASC,ts", + "order by `method` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -807,7 +818,7 @@ var testBuildTracesQueryData = []struct { "from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND has(stringTagMap, 'method') group by `method`,ts " + - "order by `method` ASC,ts", + "order by `method` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -828,7 +839,7 @@ var testBuildTracesQueryData = []struct { ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, stringTagMap['method'] as `method`" + ", sum(bytes)/60 as value from signoz_traces.distributed_signoz_index_v2 " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " AND has(stringTagMap, 'method') group by `method`,ts order by `method` ASC,ts", + " AND has(stringTagMap, 'method') group by `method`,ts order by `method` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -850,7 +861,7 @@ var testBuildTracesQueryData = []struct { ", count(numberTagMap['bytes'])/60 as value " + "from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND has(stringTagMap, 'method') group by `method`,ts " + - "order by `method` ASC,ts", + "order by `method` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -873,7 +884,7 @@ var testBuildTracesQueryData = []struct { "sum(numberTagMap['bytes'])/60 as value " + "from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND has(stringTagMap, 'method') group by `method`,ts " + - "order by `method` ASC,ts", + "order by `method` ASC", PanelType: v3.PanelTypeGraph, }, { @@ -897,7 +908,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name']))) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + - " group by ts having value > 10 order by ts", + " group by ts having value > 10 order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -925,7 +936,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value from " + "signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + - "AND stringTagMap['method'] = 'GET' AND has(stringTagMap, 'name') group by ts having value > 10 order by ts", + "AND stringTagMap['method'] = 'GET' AND has(stringTagMap, 'name') group by ts having value > 10 order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -953,7 +964,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name']))) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + - "AND stringTagMap['method'] = 'GET' group by ts having value > 10 order by ts", + "AND stringTagMap['method'] = 'GET' group by ts having value > 10 order by value DESC", PanelType: v3.PanelTypeGraph, }, { @@ -981,7 +992,7 @@ var testBuildTracesQueryData = []struct { TableName: "signoz_traces.distributed_signoz_index_v2", ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + - "AND stringTagMap['method'] = 'GET' AND has(stringTagMap, 'name') group by ts having value > 10 order by ts", + "AND stringTagMap['method'] = 'GET' AND has(stringTagMap, 'name') group by ts having value > 10", PanelType: v3.PanelTypeValue, }, { @@ -1120,7 +1131,218 @@ func TestBuildTracesQuery(t *testing.T) { Convey("TestBuildTracesQuery", t, func() { query, err := buildTracesQuery(tt.Start, tt.End, tt.Step, tt.BuilderQuery, tt.TableName, map[string]v3.AttributeKey{ "name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, - }, tt.PanelType) + }, tt.PanelType, "") + So(err, ShouldBeNil) + So(query, ShouldEqual, tt.ExpectedQuery) + }) + } +} + +var testPrepTracesQueryData = []struct { + Name string + PanelType v3.PanelType + Start int64 + End int64 + BuilderQuery *v3.BuilderQuery + ExpectedQuery string + Keys map[string]v3.AttributeKey + Type string +}{ + { + Name: "Test TS with limit- first", + PanelType: v3.PanelTypeGraph, + Start: 1680066360726210000, + End: 1680066458000000000, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="}, + }, + }, + Limit: 10, + StepInterval: 60, + GroupBy: []v3.AttributeKey{{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + }, + ExpectedQuery: "SELECT `method` from (SELECT stringTagMap['method'] as `method`," + + " toFloat64(count(distinct(stringTagMap['name']))) as value from signoz_traces.distributed_signoz_index_v2" + + " where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND" + + " stringTagMap['method'] = 'GET' AND has(stringTagMap, 'method') group by `method` order by value DESC) LIMIT 10", + Keys: map[string]v3.AttributeKey{"name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + Type: constants.FirstQueryGraphLimit, + }, + { + Name: "Test TS with limit- first - with order by value", + PanelType: v3.PanelTypeGraph, + Start: 1680066360726210000, + End: 1680066458000000000, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="}, + }, + }, + Limit: 10, + StepInterval: 60, + GroupBy: []v3.AttributeKey{{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "ASC"}}, + }, + ExpectedQuery: "SELECT `method` from (SELECT stringTagMap['method'] as `method`," + + " toFloat64(count(distinct(stringTagMap['name']))) as value from " + + "signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" + + " AND timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' AND" + + " has(stringTagMap, 'method') group by `method` order by value ASC) LIMIT 10", + Keys: map[string]v3.AttributeKey{}, + Type: constants.FirstQueryGraphLimit, + }, + { + Name: "Test TS with limit- first - with order by attribute", + PanelType: v3.PanelTypeGraph, + Start: 1680066360726210000, + End: 1680066458000000000, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Expression: "A", + Filters: &v3.FilterSet{}, + Limit: 10, + StepInterval: 60, + GroupBy: []v3.AttributeKey{{Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + OrderBy: []v3.OrderBy{{ColumnName: "serviceName", Order: "ASC"}}, + }, + ExpectedQuery: "SELECT `serviceName` from (SELECT serviceName as `serviceName`," + + " toFloat64(count(distinct(serviceName))) as value from " + + "signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" + + " AND timestamp <= '1680066458000000000') " + + "group by `serviceName` order by `serviceName` ASC) LIMIT 10", + Keys: map[string]v3.AttributeKey{}, + Type: constants.FirstQueryGraphLimit, + }, + { + Name: "Test TS with limit- first - with 2 group by and 2 order by", + PanelType: v3.PanelTypeGraph, + Start: 1680066360726210000, + End: 1680066458000000000, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Expression: "A", + Filters: &v3.FilterSet{}, + Limit: 10, + StepInterval: 60, + GroupBy: []v3.AttributeKey{ + {Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, + {Key: "http.method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + }, + OrderBy: []v3.OrderBy{{ColumnName: "serviceName", Order: "ASC"}, {ColumnName: constants.SigNozOrderByValue, Order: "ASC"}}, + }, + ExpectedQuery: "SELECT `serviceName`,`http.method` from (SELECT serviceName as `serviceName`," + + " stringTagMap['http.method'] as `http.method`," + + " toFloat64(count(distinct(serviceName))) as value from " + + "signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" + + " AND timestamp <= '1680066458000000000') AND has(stringTagMap, 'http.method') " + + "group by `serviceName`,`http.method` order by `serviceName` ASC,value ASC) LIMIT 10", + Keys: map[string]v3.AttributeKey{}, + Type: constants.FirstQueryGraphLimit, + }, + { + Name: "Test TS with limit- second", + PanelType: v3.PanelTypeGraph, + Start: 1680066360726210000, + End: 1680066458000000000, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="}, + }, + }, + GroupBy: []v3.AttributeKey{{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + Limit: 2, + StepInterval: 60, + }, + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, " + + "stringTagMap['method'] as `method`, toFloat64(count(distinct(stringTagMap['name'])))" + + " as value from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" + + " AND timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' AND" + + " has(stringTagMap, 'method') AND (`method`) GLOBAL IN (%s) group by `method`,ts order by value DESC", + Keys: map[string]v3.AttributeKey{}, + Type: constants.SecondQueryGraphLimit, + }, + { + Name: "Test TS with limit- second - with order by", + PanelType: v3.PanelTypeGraph, + Start: 1680066360726210000, + End: 1680066458000000000, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="}, + }, + }, + GroupBy: []v3.AttributeKey{{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, + OrderBy: []v3.OrderBy{{ColumnName: "method", Order: "ASC"}}, + Limit: 2, + StepInterval: 60, + }, + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, " + + "stringTagMap['method'] as `method`, toFloat64(count(distinct(stringTagMap['name'])))" + + " as value from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" + + " AND timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' AND" + + " has(stringTagMap, 'method') AND (`method`) GLOBAL IN (%s) group by `method`,ts order by `method` ASC", Keys: map[string]v3.AttributeKey{}, + Type: constants.SecondQueryGraphLimit, + }, + { + Name: "Test TS with limit - second - with two group by and two order by", + PanelType: v3.PanelTypeGraph, + Start: 1680066360726210000, + End: 1680066458000000000, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + AggregateOperator: v3.AggregateOperatorCountDistinct, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="}, + }, + }, + GroupBy: []v3.AttributeKey{ + {Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, + }, + OrderBy: []v3.OrderBy{{ColumnName: "method", Order: "ASC"}, {ColumnName: "name", Order: "ASC"}}, + Limit: 2, + StepInterval: 60, + }, + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, " + + "stringTagMap['method'] as `method`, stringTagMap['name'] as `name`," + + " toFloat64(count(distinct(stringTagMap['name'])))" + + " as value from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" + + " AND timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' AND" + + " has(stringTagMap, 'method') AND has(stringTagMap, 'name') " + + "AND (`method`,`name`) GLOBAL IN (%s) group by `method`,`name`,ts " + + "order by `method` ASC,`name` ASC", + Keys: map[string]v3.AttributeKey{}, + Type: constants.SecondQueryGraphLimit, + }, +} + +func TestPrepareTracesQuery(t *testing.T) { + for _, tt := range testPrepTracesQueryData { + Convey("TestPrepareTracesQuery", t, func() { + query, err := PrepareTracesQuery(tt.Start, tt.End, tt.PanelType, tt.BuilderQuery, tt.Keys, tt.Type) So(err, ShouldBeNil) So(query, ShouldEqual, tt.ExpectedQuery) }) From 98a2ef4080580ee9f31f782918d1bd22881d730a Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Wed, 19 Jul 2023 08:47:21 +0300 Subject: [PATCH 10/13] feat: add suggestion to order by filter (#3162) * feat: add suggestion to order by filter * fix: column name for order by * fix: mapper for order by * fix: render order by for different panels * fix: order by timestamp and aggrigate value --------- Co-authored-by: Palash Gupta Co-authored-by: Vishal Sharma --- .../src/container/ExplorerOrderBy/index.tsx | 72 ++++++ .../LogExplorerQuerySection/index.tsx | 20 +- .../src/container/LogsExplorerViews/index.tsx | 41 +++- .../container/OptionsMenu/useOptionsMenu.ts | 16 +- .../QueryBuilder/QueryBuilder.interfaces.ts | 3 + .../container/QueryBuilder/QueryBuilder.tsx | 2 + .../components/Query/Query.interfaces.ts | 2 +- .../QueryBuilder/components/Query/Query.tsx | 26 ++- .../filters/GroupByFilter/GroupByFilter.tsx | 20 +- .../filters/OrderByFilter/OrderByFilter.tsx | 217 +++--------------- .../filters/OrderByFilter/constants.ts | 1 + .../filters/OrderByFilter/useOrderByFilter.ts | 199 ++++++++++++++++ .../filters/OrderByFilter/utils.ts | 45 ++-- .../TracesExplorer/QuerySection/index.tsx | 21 +- .../queryBuilder/useFetchKeysAndValues.ts | 23 +- .../hooks/queryBuilder/useGetAggregateKeys.ts | 34 +++ frontend/src/pages/TracesExplorer/index.tsx | 49 ++-- frontend/src/providers/QueryBuilder.tsx | 19 ++ frontend/src/types/common/queryBuilder.ts | 8 + 19 files changed, 539 insertions(+), 279 deletions(-) create mode 100644 frontend/src/container/ExplorerOrderBy/index.tsx create mode 100644 frontend/src/container/QueryBuilder/filters/OrderByFilter/constants.ts create mode 100644 frontend/src/container/QueryBuilder/filters/OrderByFilter/useOrderByFilter.ts create mode 100644 frontend/src/hooks/queryBuilder/useGetAggregateKeys.ts 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 ( +
Empty