diff --git a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx index da7bbbfc60..9ce242582b 100644 --- a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx @@ -14,10 +14,12 @@ import { resourceAttributesToTagFilterItems, } from 'hooks/useResourceAttribute/utils'; import useUrlQuery from 'hooks/useUrlQuery'; +import getStep from 'lib/getStep'; import history from 'lib/history'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useLocation, useParams } from 'react-router-dom'; +import store from 'store'; import { UpdateTimeInterval } from 'store/actions'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; @@ -123,6 +125,16 @@ function DBCall(): JSX.Element { [servicename, tagFilterItems], ); + const stepInterval = useMemo( + () => + getStep({ + end: store.getState().globalTime.maxTime, + inputFormat: 'ns', + start: store.getState().globalTime.minTime, + }), + [], + ); + const logEventCalledRef = useRef(false); useEffect(() => { @@ -158,6 +170,7 @@ function DBCall(): JSX.Element { selectedTraceTags, timestamp: selectedTimeStamp, apmToTraceQuery, + stepInterval, })} > View Traces @@ -192,6 +205,7 @@ function DBCall(): JSX.Element { selectedTraceTags, timestamp: selectedTimeStamp, apmToTraceQuery, + stepInterval, })} > View Traces diff --git a/frontend/src/container/MetricsApplication/Tabs/External.tsx b/frontend/src/container/MetricsApplication/Tabs/External.tsx index 5ba3d3df6c..328a544472 100644 --- a/frontend/src/container/MetricsApplication/Tabs/External.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/External.tsx @@ -16,10 +16,12 @@ import { resourceAttributesToTagFilterItems, } from 'hooks/useResourceAttribute/utils'; import useUrlQuery from 'hooks/useUrlQuery'; +import getStep from 'lib/getStep'; import history from 'lib/history'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useLocation, useParams } from 'react-router-dom'; +import store from 'store'; import { UpdateTimeInterval } from 'store/actions'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { EQueryType } from 'types/common/dashboard'; @@ -141,6 +143,15 @@ function External(): JSX.Element { ], }); + const stepInterval = useMemo( + () => + getStep({ + end: store.getState().globalTime.maxTime, + inputFormat: 'ns', + start: store.getState().globalTime.minTime, + }), + [], + ); const logEventCalledRef = useRef(false); useEffect(() => { if (!logEventCalledRef.current) { @@ -222,6 +233,7 @@ function External(): JSX.Element { selectedTraceTags, timestamp: selectedTimeStamp, apmToTraceQuery: errorApmToTraceQuery, + stepInterval, })} > View Traces @@ -257,6 +269,7 @@ function External(): JSX.Element { selectedTraceTags, timestamp: selectedTimeStamp, apmToTraceQuery, + stepInterval, })} > View Traces @@ -295,6 +308,7 @@ function External(): JSX.Element { selectedTraceTags, timestamp: selectedTimeStamp, apmToTraceQuery, + stepInterval, })} > View Traces @@ -330,6 +344,7 @@ function External(): JSX.Element { selectedTraceTags, timestamp: selectedTimeStamp, apmToTraceQuery, + stepInterval, })} > View Traces diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx index 241395b23a..77bf86b80f 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx @@ -15,6 +15,7 @@ import { resourceAttributesToTagFilterItems, } from 'hooks/useResourceAttribute/utils'; import useUrlQuery from 'hooks/useUrlQuery'; +import getStep from 'lib/getStep'; import history from 'lib/history'; import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin'; import { defaultTo } from 'lodash-es'; @@ -38,6 +39,7 @@ import { } from '../MetricsPageQueries/OverviewQueries'; import { Col, ColApDexContainer, ColErrorContainer, Row } from '../styles'; import ApDex from './Overview/ApDex'; +import GraphControlsPanel from './Overview/GraphControlsPanel/GraphControlsPanel'; import ServiceOverview from './Overview/ServiceOverview'; import TopLevelOperation from './Overview/TopLevelOperations'; import TopOperation from './Overview/TopOperation'; @@ -45,9 +47,11 @@ import TopOperationMetrics from './Overview/TopOperationMetrics'; import { Button, Card } from './styles'; import { IServiceName } from './types'; import { + generateExplorerPath, handleNonInQueryRange, onGraphClickHandler, onViewTracePopupClick, + useGetAPMToLogsQueries, useGetAPMToTracesQueries, } from './util'; @@ -177,6 +181,16 @@ function Application(): JSX.Element { id: SERVICE_CHART_ID.errorPercentage, }); + const stepInterval = useMemo( + () => + getStep({ + end: maxTime, + inputFormat: 'ns', + start: minTime, + }), + [maxTime, minTime], + ); + const onDragSelect = useCallback( (start: number, end: number) => { const startTimestamp = Math.trunc(start); @@ -194,33 +208,60 @@ function Application(): JSX.Element { [dispatch, pathname, urlQuery], ); - const onErrorTrackHandler = ( - timestamp: number, - apmToTraceQuery: Query, - ): (() => void) => (): void => { - const currentTime = timestamp; - const tPlusOne = timestamp + 60 * 1000; + const onErrorTrackHandler = useCallback( + ( + timestamp: number, + apmToTraceQuery: Query, + isViewLogsClicked?: boolean, + ): (() => void) => (): void => { + const currentTime = timestamp; + const endTime = timestamp + stepInterval; + console.log(endTime, stepInterval); - const urlParams = new URLSearchParams(search); - urlParams.set(QueryParams.startTime, currentTime.toString()); - urlParams.set(QueryParams.endTime, tPlusOne.toString()); + const urlParams = new URLSearchParams(search); + urlParams.set(QueryParams.startTime, currentTime.toString()); + urlParams.set(QueryParams.endTime, endTime.toString()); + urlParams.delete(QueryParams.relativeTime); + const avialableParams = routeConfig[ROUTES.TRACE]; + const queryString = getQueryString(avialableParams, urlParams); - const avialableParams = routeConfig[ROUTES.TRACE]; - const queryString = getQueryString(avialableParams, urlParams); + const JSONCompositeQuery = encodeURIComponent( + JSON.stringify(apmToTraceQuery), + ); - const JSONCompositeQuery = encodeURIComponent( - JSON.stringify(apmToTraceQuery), - ); + const newPath = generateExplorerPath( + isViewLogsClicked, + urlParams, + servicename, + selectedTraceTags, + JSONCompositeQuery, + queryString, + ); - const newTraceExplorerPath = `${ - ROUTES.TRACES_EXPLORER - }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&${ - QueryParams.compositeQuery - }=${JSONCompositeQuery}&${queryString.join('&')}`; - - history.push(newTraceExplorerPath); - }; + history.push(newPath); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [stepInterval], + ); + const logErrorQuery = useGetAPMToLogsQueries({ + servicename, + filters: [ + { + id: uuid().slice(0, 8), + key: { + key: 'severity_text', + dataType: DataTypes.String, + type: '', + isColumn: true, + isJSON: false, + id: 'severity_text--string----true', + }, + op: 'in', + value: ['ERROR', 'FATAL', 'error', 'fatal'], + }, + ], + }); const errorTrackQuery = useGetAPMToTracesQueries({ servicename, filters: [ @@ -251,6 +292,7 @@ function Application(): JSX.Element { selectedTraceTags={selectedTraceTags} topLevelOperationsRoute={topLevelOperationsRoute} topLevelOperationsIsLoading={topLevelOperationsIsLoading} + stepInterval={stepInterval} /> @@ -264,6 +306,7 @@ function Application(): JSX.Element { selectedTraceTags, timestamp: selectedTimeStamp, apmToTraceQuery, + stepInterval, })} > View Traces @@ -292,6 +335,7 @@ function Application(): JSX.Element { selectedTraceTags, timestamp: selectedTimeStamp, apmToTraceQuery, + stepInterval, })} > View Traces @@ -304,14 +348,18 @@ function Application(): JSX.Element { /> - + onViewLogsClick={onErrorTrackHandler( + selectedTimeStamp, + logErrorQuery, + true, + )} + onViewTracesClick={onErrorTrackHandler( + selectedTimeStamp, + errorTrackQuery, + )} + /> void; + onViewTracesClick: () => void; +} + +function GraphControlsPanel({ + id, + onViewLogsClick, + onViewTracesClick, +}: GraphControlsPanelProps): JSX.Element { + return ( +
+ + +
+ ); +} + +export default GraphControlsPanel; diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx index 9651e16d3a..24e2233244 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx @@ -19,13 +19,14 @@ import { useParams } from 'react-router-dom'; import { EQueryType } from 'types/common/dashboard'; import { v4 as uuid } from 'uuid'; -import { Button } from '../styles'; import { IServiceName } from '../types'; import { handleNonInQueryRange, onViewTracePopupClick, + useGetAPMToLogsQueries, useGetAPMToTracesQueries, } from '../util'; +import GraphControlsPanel from './GraphControlsPanel/GraphControlsPanel'; function ServiceOverview({ onDragSelect, @@ -34,6 +35,7 @@ function ServiceOverview({ selectedTimeStamp, topLevelOperationsRoute, topLevelOperationsIsLoading, + stepInterval, }: ServiceOverviewProps): JSX.Element { const { servicename: encodedServiceName } = useParams(); const servicename = decodeURIComponent(encodedServiceName); @@ -75,21 +77,28 @@ function ServiceOverview({ const apmToTraceQuery = useGetAPMToTracesQueries({ servicename }); + const apmToLogQuery = useGetAPMToLogsQueries({ servicename }); + return ( <> - + /> {topLevelOperationsIsLoading && ( @@ -114,8 +123,8 @@ function ServiceOverview({ ); } - interface ServiceOverviewProps { + stepInterval: number; selectedTimeStamp: number; selectedTraceTags: string; onDragSelect: (start: number, end: number) => void; diff --git a/frontend/src/container/MetricsApplication/Tabs/util.ts b/frontend/src/container/MetricsApplication/Tabs/util.ts index 3e4dbeceb4..e6d58831a0 100644 --- a/frontend/src/container/MetricsApplication/Tabs/util.ts +++ b/frontend/src/container/MetricsApplication/Tabs/util.ts @@ -7,6 +7,7 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import useResourceAttribute from 'hooks/useResourceAttribute'; import { resourceAttributesToTracesFilterItems } from 'hooks/useResourceAttribute/utils'; import history from 'lib/history'; +import { prepareQueryWithDefaultTimestamp } from 'pages/LogsExplorer/utils'; import { traceFilterKeys } from 'pages/TracesExplorer/Filter/filterUtils'; import { Dispatch, SetStateAction, useMemo } from 'react'; import { @@ -33,21 +34,44 @@ interface OnViewTracePopupClickProps { selectedTraceTags: string; timestamp: number; apmToTraceQuery: Query; + isViewLogsClicked?: boolean; + stepInterval?: number; } + +export function generateExplorerPath( + isViewLogsClicked: boolean | undefined, + urlParams: URLSearchParams, + servicename: string | undefined, + selectedTraceTags: string, + JSONCompositeQuery: string, + queryString: string[], +): string { + const basePath = isViewLogsClicked + ? ROUTES.LOGS_EXPLORER + : ROUTES.TRACES_EXPLORER; + + return `${basePath}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&${ + QueryParams.compositeQuery + }=${JSONCompositeQuery}&${queryString.join('&')}`; +} + +// TODO(@rahul-signoz): update the name of this function once we have view logs button in every panel export function onViewTracePopupClick({ selectedTraceTags, servicename, timestamp, apmToTraceQuery, + isViewLogsClicked, + stepInterval, }: OnViewTracePopupClickProps): VoidFunction { return (): void => { const currentTime = timestamp; - - const tPlusOne = timestamp + 60; + const endTime = timestamp + (stepInterval || 60); const urlParams = new URLSearchParams(window.location.search); urlParams.set(QueryParams.startTime, currentTime.toString()); - urlParams.set(QueryParams.endTime, tPlusOne.toString()); + urlParams.set(QueryParams.endTime, endTime.toString()); + urlParams.delete(QueryParams.relativeTime); const avialableParams = routeConfig[ROUTES.TRACE]; const queryString = getQueryString(avialableParams, urlParams); @@ -55,13 +79,16 @@ export function onViewTracePopupClick({ JSON.stringify(apmToTraceQuery), ); - const newTraceExplorerPath = `${ - ROUTES.TRACES_EXPLORER - }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&${ - QueryParams.compositeQuery - }=${JSONCompositeQuery}&${queryString.join('&')}`; + const newPath = generateExplorerPath( + isViewLogsClicked, + urlParams, + servicename, + selectedTraceTags, + JSONCompositeQuery, + queryString, + ); - history.push(newTraceExplorerPath); + history.push(newPath); }; } @@ -108,12 +135,13 @@ export function handleQueryChange( attributeKeys: BaseAutocompleteData, serviceAttribute: string, filters?: TagFilterItem[], + logs?: boolean, ): Query { const filterItem: TagFilterItem[] = [ { id: uuid().slice(0, 8), key: attributeKeys, - op: 'in', + op: logs ? '=' : 'in', value: serviceAttribute, }, ]; @@ -132,6 +160,42 @@ export function handleQueryChange( }; } +export function useGetAPMToLogsQueries({ + servicename, + filters, +}: { + servicename: string; + filters?: TagFilterItem[]; +}): Query { + const finalFilters: TagFilterItem[] = []; + const { updateAllQueriesOperators } = useQueryBuilder(); + let updatedQuery = updateAllQueriesOperators( + initialQueriesMap.logs, + PANEL_TYPES.LIST, + DataSource.LOGS, + ); + const serviceName = { + id: 'service.name--string--resource--true', + dataType: DataTypes.String, + isColumn: false, + key: 'service.name', + type: 'resource', + isJSON: false, + }; + + if (filters?.length) { + finalFilters.push(...filters); + } + updatedQuery = prepareQueryWithDefaultTimestamp(updatedQuery); + return handleQueryChange( + updatedQuery, + serviceName, + servicename, + finalFilters, + true, + ); +} + export function useGetAPMToTracesQueries({ servicename, isExternalCall,