Merge pull request #5826 from SigNoz/SIG-5729

feat: added view logs button for error and latency chart
This commit is contained in:
rahulkeswani101 2024-10-04 20:04:50 +05:30 committed by GitHub
commit 69aab87d72
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 265 additions and 48 deletions

View File

@ -14,10 +14,12 @@ import {
resourceAttributesToTagFilterItems, resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils'; } from 'hooks/useResourceAttribute/utils';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import history from 'lib/history'; import history from 'lib/history';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import store from 'store';
import { UpdateTimeInterval } from 'store/actions'; import { UpdateTimeInterval } from 'store/actions';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
@ -123,6 +125,16 @@ function DBCall(): JSX.Element {
[servicename, tagFilterItems], [servicename, tagFilterItems],
); );
const stepInterval = useMemo(
() =>
getStep({
end: store.getState().globalTime.maxTime,
inputFormat: 'ns',
start: store.getState().globalTime.minTime,
}),
[],
);
const logEventCalledRef = useRef(false); const logEventCalledRef = useRef(false);
useEffect(() => { useEffect(() => {
@ -158,6 +170,7 @@ function DBCall(): JSX.Element {
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery, apmToTraceQuery,
stepInterval,
})} })}
> >
View Traces View Traces
@ -192,6 +205,7 @@ function DBCall(): JSX.Element {
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery, apmToTraceQuery,
stepInterval,
})} })}
> >
View Traces View Traces

View File

@ -16,10 +16,12 @@ import {
resourceAttributesToTagFilterItems, resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils'; } from 'hooks/useResourceAttribute/utils';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import history from 'lib/history'; import history from 'lib/history';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import store from 'store';
import { UpdateTimeInterval } from 'store/actions'; import { UpdateTimeInterval } from 'store/actions';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard'; 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); const logEventCalledRef = useRef(false);
useEffect(() => { useEffect(() => {
if (!logEventCalledRef.current) { if (!logEventCalledRef.current) {
@ -222,6 +233,7 @@ function External(): JSX.Element {
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery: errorApmToTraceQuery, apmToTraceQuery: errorApmToTraceQuery,
stepInterval,
})} })}
> >
View Traces View Traces
@ -257,6 +269,7 @@ function External(): JSX.Element {
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery, apmToTraceQuery,
stepInterval,
})} })}
> >
View Traces View Traces
@ -295,6 +308,7 @@ function External(): JSX.Element {
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery, apmToTraceQuery,
stepInterval,
})} })}
> >
View Traces View Traces
@ -330,6 +344,7 @@ function External(): JSX.Element {
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery, apmToTraceQuery,
stepInterval,
})} })}
> >
View Traces View Traces

View File

@ -15,6 +15,7 @@ import {
resourceAttributesToTagFilterItems, resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils'; } from 'hooks/useResourceAttribute/utils';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import history from 'lib/history'; import history from 'lib/history';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin'; import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { defaultTo } from 'lodash-es'; import { defaultTo } from 'lodash-es';
@ -38,6 +39,7 @@ import {
} from '../MetricsPageQueries/OverviewQueries'; } from '../MetricsPageQueries/OverviewQueries';
import { Col, ColApDexContainer, ColErrorContainer, Row } from '../styles'; import { Col, ColApDexContainer, ColErrorContainer, Row } from '../styles';
import ApDex from './Overview/ApDex'; import ApDex from './Overview/ApDex';
import GraphControlsPanel from './Overview/GraphControlsPanel/GraphControlsPanel';
import ServiceOverview from './Overview/ServiceOverview'; import ServiceOverview from './Overview/ServiceOverview';
import TopLevelOperation from './Overview/TopLevelOperations'; import TopLevelOperation from './Overview/TopLevelOperations';
import TopOperation from './Overview/TopOperation'; import TopOperation from './Overview/TopOperation';
@ -45,9 +47,11 @@ import TopOperationMetrics from './Overview/TopOperationMetrics';
import { Button, Card } from './styles'; import { Button, Card } from './styles';
import { IServiceName } from './types'; import { IServiceName } from './types';
import { import {
generateExplorerPath,
handleNonInQueryRange, handleNonInQueryRange,
onGraphClickHandler, onGraphClickHandler,
onViewTracePopupClick, onViewTracePopupClick,
useGetAPMToLogsQueries,
useGetAPMToTracesQueries, useGetAPMToTracesQueries,
} from './util'; } from './util';
@ -177,6 +181,16 @@ function Application(): JSX.Element {
id: SERVICE_CHART_ID.errorPercentage, id: SERVICE_CHART_ID.errorPercentage,
}); });
const stepInterval = useMemo(
() =>
getStep({
end: maxTime,
inputFormat: 'ns',
start: minTime,
}),
[maxTime, minTime],
);
const onDragSelect = useCallback( const onDragSelect = useCallback(
(start: number, end: number) => { (start: number, end: number) => {
const startTimestamp = Math.trunc(start); const startTimestamp = Math.trunc(start);
@ -194,33 +208,60 @@ function Application(): JSX.Element {
[dispatch, pathname, urlQuery], [dispatch, pathname, urlQuery],
); );
const onErrorTrackHandler = ( const onErrorTrackHandler = useCallback(
timestamp: number, (
apmToTraceQuery: Query, timestamp: number,
): (() => void) => (): void => { apmToTraceQuery: Query,
const currentTime = timestamp; isViewLogsClicked?: boolean,
const tPlusOne = timestamp + 60 * 1000; ): (() => void) => (): void => {
const currentTime = timestamp;
const endTime = timestamp + stepInterval;
console.log(endTime, stepInterval);
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
urlParams.set(QueryParams.startTime, currentTime.toString()); 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);
const avialableParams = routeConfig[ROUTES.TRACE]; const JSONCompositeQuery = encodeURIComponent(
const queryString = getQueryString(avialableParams, urlParams); JSON.stringify(apmToTraceQuery),
);
const JSONCompositeQuery = encodeURIComponent( const newPath = generateExplorerPath(
JSON.stringify(apmToTraceQuery), isViewLogsClicked,
); urlParams,
servicename,
selectedTraceTags,
JSONCompositeQuery,
queryString,
);
const newTraceExplorerPath = `${ history.push(newPath);
ROUTES.TRACES_EXPLORER },
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&${ // eslint-disable-next-line react-hooks/exhaustive-deps
QueryParams.compositeQuery [stepInterval],
}=${JSONCompositeQuery}&${queryString.join('&')}`; );
history.push(newTraceExplorerPath);
};
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({ const errorTrackQuery = useGetAPMToTracesQueries({
servicename, servicename,
filters: [ filters: [
@ -251,6 +292,7 @@ function Application(): JSX.Element {
selectedTraceTags={selectedTraceTags} selectedTraceTags={selectedTraceTags}
topLevelOperationsRoute={topLevelOperationsRoute} topLevelOperationsRoute={topLevelOperationsRoute}
topLevelOperationsIsLoading={topLevelOperationsIsLoading} topLevelOperationsIsLoading={topLevelOperationsIsLoading}
stepInterval={stepInterval}
/> />
</Col> </Col>
@ -264,6 +306,7 @@ function Application(): JSX.Element {
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery, apmToTraceQuery,
stepInterval,
})} })}
> >
View Traces View Traces
@ -292,6 +335,7 @@ function Application(): JSX.Element {
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery, apmToTraceQuery,
stepInterval,
})} })}
> >
View Traces View Traces
@ -304,14 +348,18 @@ function Application(): JSX.Element {
/> />
</ColApDexContainer> </ColApDexContainer>
<ColErrorContainer> <ColErrorContainer>
<Button <GraphControlsPanel
type="default"
size="small"
id="Error_button" id="Error_button"
onClick={onErrorTrackHandler(selectedTimeStamp, errorTrackQuery)} onViewLogsClick={onErrorTrackHandler(
> selectedTimeStamp,
View Traces logErrorQuery,
</Button> true,
)}
onViewTracesClick={onErrorTrackHandler(
selectedTimeStamp,
errorTrackQuery,
)}
/>
<TopLevelOperation <TopLevelOperation
handleGraphClick={handleGraphClick} handleGraphClick={handleGraphClick}

View File

@ -0,0 +1,25 @@
.graph-controls-panel {
position: absolute;
z-index: 999;
display: none;
width: 110px;
padding: 5px;
border-radius: 5px;
background: var(--bg-slate-400);
.ant-btn-link {
padding: 0;
margin: 0;
text-decoration: none;
color: var(--bg-vanilla-100);
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 2px;
border: none;
}
.ant-btn-link:hover {
color: var(--bg-vanilla-100);
}
}

View File

@ -0,0 +1,42 @@
import './GraphControlsPanel.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { DraftingCompass, ScrollText } from 'lucide-react';
interface GraphControlsPanelProps {
id: string;
onViewLogsClick: () => void;
onViewTracesClick: () => void;
}
function GraphControlsPanel({
id,
onViewLogsClick,
onViewTracesClick,
}: GraphControlsPanelProps): JSX.Element {
return (
<div id={id} className="graph-controls-panel">
<Button
type="link"
icon={<DraftingCompass size={14} />}
size="small"
onClick={onViewTracesClick}
style={{ color: Color.BG_VANILLA_100 }}
>
View traces
</Button>
<Button
type="link"
icon={<ScrollText size={14} />}
size="small"
onClick={onViewLogsClick}
style={{ color: Color.BG_VANILLA_100 }}
>
View logs
</Button>
</div>
);
}
export default GraphControlsPanel;

View File

@ -19,13 +19,14 @@ import { useParams } from 'react-router-dom';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { Button } from '../styles';
import { IServiceName } from '../types'; import { IServiceName } from '../types';
import { import {
handleNonInQueryRange, handleNonInQueryRange,
onViewTracePopupClick, onViewTracePopupClick,
useGetAPMToLogsQueries,
useGetAPMToTracesQueries, useGetAPMToTracesQueries,
} from '../util'; } from '../util';
import GraphControlsPanel from './GraphControlsPanel/GraphControlsPanel';
function ServiceOverview({ function ServiceOverview({
onDragSelect, onDragSelect,
@ -34,6 +35,7 @@ function ServiceOverview({
selectedTimeStamp, selectedTimeStamp,
topLevelOperationsRoute, topLevelOperationsRoute,
topLevelOperationsIsLoading, topLevelOperationsIsLoading,
stepInterval,
}: ServiceOverviewProps): JSX.Element { }: ServiceOverviewProps): JSX.Element {
const { servicename: encodedServiceName } = useParams<IServiceName>(); const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName); const servicename = decodeURIComponent(encodedServiceName);
@ -75,21 +77,28 @@ function ServiceOverview({
const apmToTraceQuery = useGetAPMToTracesQueries({ servicename }); const apmToTraceQuery = useGetAPMToTracesQueries({ servicename });
const apmToLogQuery = useGetAPMToLogsQueries({ servicename });
return ( return (
<> <>
<Button <GraphControlsPanel
type="default"
size="small"
id="Service_button" id="Service_button"
onClick={onViewTracePopupClick({ onViewLogsClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery: apmToLogQuery,
isViewLogsClicked: true,
stepInterval,
})}
onViewTracesClick={onViewTracePopupClick({
servicename, servicename,
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery, apmToTraceQuery,
stepInterval,
})} })}
> />
View Traces
</Button>
<Card data-testid="service_latency"> <Card data-testid="service_latency">
<GraphContainer> <GraphContainer>
{topLevelOperationsIsLoading && ( {topLevelOperationsIsLoading && (
@ -114,8 +123,8 @@ function ServiceOverview({
</> </>
); );
} }
interface ServiceOverviewProps { interface ServiceOverviewProps {
stepInterval: number;
selectedTimeStamp: number; selectedTimeStamp: number;
selectedTraceTags: string; selectedTraceTags: string;
onDragSelect: (start: number, end: number) => void; onDragSelect: (start: number, end: number) => void;

View File

@ -7,6 +7,7 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useResourceAttribute from 'hooks/useResourceAttribute'; import useResourceAttribute from 'hooks/useResourceAttribute';
import { resourceAttributesToTracesFilterItems } from 'hooks/useResourceAttribute/utils'; import { resourceAttributesToTracesFilterItems } from 'hooks/useResourceAttribute/utils';
import history from 'lib/history'; import history from 'lib/history';
import { prepareQueryWithDefaultTimestamp } from 'pages/LogsExplorer/utils';
import { traceFilterKeys } from 'pages/TracesExplorer/Filter/filterUtils'; import { traceFilterKeys } from 'pages/TracesExplorer/Filter/filterUtils';
import { Dispatch, SetStateAction, useMemo } from 'react'; import { Dispatch, SetStateAction, useMemo } from 'react';
import { import {
@ -33,21 +34,44 @@ interface OnViewTracePopupClickProps {
selectedTraceTags: string; selectedTraceTags: string;
timestamp: number; timestamp: number;
apmToTraceQuery: Query; 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({ export function onViewTracePopupClick({
selectedTraceTags, selectedTraceTags,
servicename, servicename,
timestamp, timestamp,
apmToTraceQuery, apmToTraceQuery,
isViewLogsClicked,
stepInterval,
}: OnViewTracePopupClickProps): VoidFunction { }: OnViewTracePopupClickProps): VoidFunction {
return (): void => { return (): void => {
const currentTime = timestamp; const currentTime = timestamp;
const endTime = timestamp + (stepInterval || 60);
const tPlusOne = timestamp + 60;
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
urlParams.set(QueryParams.startTime, currentTime.toString()); 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 avialableParams = routeConfig[ROUTES.TRACE];
const queryString = getQueryString(avialableParams, urlParams); const queryString = getQueryString(avialableParams, urlParams);
@ -55,13 +79,16 @@ export function onViewTracePopupClick({
JSON.stringify(apmToTraceQuery), JSON.stringify(apmToTraceQuery),
); );
const newTraceExplorerPath = `${ const newPath = generateExplorerPath(
ROUTES.TRACES_EXPLORER isViewLogsClicked,
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&${ urlParams,
QueryParams.compositeQuery servicename,
}=${JSONCompositeQuery}&${queryString.join('&')}`; selectedTraceTags,
JSONCompositeQuery,
queryString,
);
history.push(newTraceExplorerPath); history.push(newPath);
}; };
} }
@ -108,12 +135,13 @@ export function handleQueryChange(
attributeKeys: BaseAutocompleteData, attributeKeys: BaseAutocompleteData,
serviceAttribute: string, serviceAttribute: string,
filters?: TagFilterItem[], filters?: TagFilterItem[],
logs?: boolean,
): Query { ): Query {
const filterItem: TagFilterItem[] = [ const filterItem: TagFilterItem[] = [
{ {
id: uuid().slice(0, 8), id: uuid().slice(0, 8),
key: attributeKeys, key: attributeKeys,
op: 'in', op: logs ? '=' : 'in',
value: serviceAttribute, 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({ export function useGetAPMToTracesQueries({
servicename, servicename,
isExternalCall, isExternalCall,