feat: navigate to trace from metrics (#2191)

* feat: navigate to trace from metrics

* chore: add sonar back

* chore: refactor
This commit is contained in:
Vishal Sharma 2023-02-08 12:41:55 +05:30 committed by GitHub
parent 47a41473df
commit d779b83715
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 274 additions and 86 deletions

View File

@ -47,7 +47,7 @@ export const databaseCallsAvgDuration = ({
const metricNameA = 'signoz_db_latency_sum';
const metricNameB = 'signoz_db_latency_count';
const expression = 'A/B';
const legendFormula = '';
const legendFormula = 'Average Duration';
const legend = '';
const disabled = true;
const additionalItemsA = [

View File

@ -4,8 +4,11 @@ import {
databaseCallsAvgDuration,
databaseCallsRPS,
} from 'container/MetricsApplication/MetricsPageQueries/DBCallQueries';
import { resourceAttributesToTagFilterItems } from 'lib/resourceAttributes';
import React, { useMemo } from 'react';
import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'lib/resourceAttributes';
import React, { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
@ -13,9 +16,16 @@ import { Widgets } from 'types/api/dashboard/getAll';
import MetricReducer from 'types/reducer/metrics';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { Button } from './styles';
import {
dbSystemTags,
onGraphClickHandler,
onViewTracePopupClick,
} from './util';
function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>();
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
(state) => state.metrics,
);
@ -23,6 +33,15 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
[resourceAttributeQueries],
);
const selectedTraceTags: string = useMemo(
() =>
JSON.stringify(
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries).concat(
...dbSystemTags,
) || [],
),
[resourceAttributeQueries],
);
const legend = '{{db_system}}';
const databaseCallsRPSWidget = useMemo(
@ -39,7 +58,6 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
const databaseCallsAverageDurationWidget = useMemo(
() =>
getWidgetQueryBuilder({
@ -57,6 +75,18 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
return (
<Row gutter={24}>
<Col span={12}>
<Button
type="default"
size="small"
id="database_call_rps_button"
onClick={onViewTracePopupClick(
servicename,
selectedTraceTags,
selectedTimeStamp,
)}
>
View Traces
</Button>
<Card>
<GraphTitle>Database Calls RPS</GraphTitle>
<GraphContainer>
@ -65,12 +95,33 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
fullViewOptions={false}
widget={databaseCallsRPSWidget}
yAxisUnit="reqps"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'database_call_rps',
);
}}
/>
</GraphContainer>
</Card>
</Col>
<Col span={12}>
<Button
type="default"
size="small"
id="database_call_avg_duration_button"
onClick={onViewTracePopupClick(
servicename,
selectedTraceTags,
selectedTimeStamp,
)}
>
View Traces
</Button>
<Card>
<GraphTitle>Database Calls Avg Duration</GraphTitle>
<GraphContainer>
@ -79,6 +130,15 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
fullViewOptions={false}
widget={databaseCallsAverageDurationWidget}
yAxisUnit="ms"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'database_call_avg_duration',
);
}}
/>
</GraphContainer>
</Card>

View File

@ -6,8 +6,11 @@ import {
externalCallErrorPercent,
externalCallRpsByAddress,
} from 'container/MetricsApplication/MetricsPageQueries/ExternalQueries';
import { resourceAttributesToTagFilterItems } from 'lib/resourceAttributes';
import React, { useMemo } from 'react';
import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'lib/resourceAttributes';
import React, { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
@ -15,18 +18,25 @@ import { Widgets } from 'types/api/dashboard/getAll';
import MetricReducer from 'types/reducer/metrics';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { Button } from './styles';
import { onGraphClickHandler, onViewTracePopupClick } from './util';
function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>();
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
(state) => state.metrics,
);
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const tagFilterItems = useMemo(
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
[resourceAttributeQueries],
);
const selectedTraceTags: string = JSON.stringify(
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
);
const legend = '{{address}}';
const externalCallErrorWidget = useMemo(
@ -92,6 +102,18 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
<>
<Row gutter={24}>
<Col span={12}>
<Button
type="default"
size="small"
id="external_call_error_percentage_button"
onClick={onViewTracePopupClick(
servicename,
selectedTraceTags,
selectedTimeStamp,
)}
>
View Traces
</Button>
<Card>
<GraphTitle>External Call Error Percentage</GraphTitle>
<GraphContainer>
@ -100,12 +122,33 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
fullViewOptions={false}
widget={externalCallErrorWidget}
yAxisUnit="%"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'external_call_error_percentage',
);
}}
/>
</GraphContainer>
</Card>
</Col>
<Col span={12}>
<Button
type="default"
size="small"
id="external_call_duration_button"
onClick={onViewTracePopupClick(
servicename,
selectedTraceTags,
selectedTimeStamp,
)}
>
View Traces
</Button>
<Card>
<GraphTitle>External Call duration</GraphTitle>
<GraphContainer>
@ -114,6 +157,15 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
fullViewOptions={false}
widget={externalCallDurationWidget}
yAxisUnit="ms"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'external_call_duration',
);
}}
/>
</GraphContainer>
</Card>
@ -122,6 +174,18 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
<Row gutter={24}>
<Col span={12}>
<Button
type="default"
size="small"
id="external_call_rps_by_address_button"
onClick={onViewTracePopupClick(
servicename,
selectedTraceTags,
selectedTimeStamp,
)}
>
View Traces
</Button>
<Card>
<GraphTitle>External Call RPS(by Address)</GraphTitle>
<GraphContainer>
@ -130,12 +194,33 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
fullViewOptions={false}
widget={externalCallRPSWidget}
yAxisUnit="reqps"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'external_call_rps_by_address',
);
}}
/>
</GraphContainer>
</Card>
</Col>
<Col span={12}>
<Button
type="default"
size="small"
id="external_call_duration_by_address_button"
onClick={onViewTracePopupClick(
servicename,
selectedTraceTags,
selectedTimeStamp,
)}
>
View Traces
</Button>
<Card>
<GraphTitle>External Call duration(by Address)</GraphTitle>
<GraphContainer>
@ -144,6 +229,15 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
fullViewOptions={false}
widget={externalCallDurationAddressWidget}
yAxisUnit="ms"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'external_call_duration_by_address',
);
}}
/>
</GraphContainer>
</Card>

View File

@ -1,4 +1,3 @@
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
import Graph from 'components/Graph';
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
import ROUTES from 'constants/routes';
@ -10,7 +9,7 @@ import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'lib/resourceAttributes';
import React, { useCallback, useMemo, useRef } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
@ -25,10 +24,11 @@ import {
import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
import TopOperationsTable from '../TopOperationsTable';
import { Button } from './styles';
import { onGraphClickHandler, onViewTracePopupClick } from './util';
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>();
const selectedTimeStamp = useRef(0);
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const dispatch = useDispatch();
const {
@ -39,7 +39,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
const selectedTraceTags: string = JSON.stringify(
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries, 'array') || [],
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
);
const tagFilterItems = useMemo(
@ -77,58 +77,6 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
[servicename, topLevelOperations, tagFilterItems, getWidgetQueryBuilder],
);
const onTracePopupClick = (timestamp: number): void => {
const currentTime = timestamp;
const tPlusOne = timestamp + 1 * 60 * 1000;
const urlParams = new URLSearchParams();
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
history.replace(
`${
ROUTES.TRACE
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1`,
);
};
const onClickHandler = async (
event: ChartEvent,
elements: ActiveElement[],
chart: Chart,
data: ChartData,
from: string,
): Promise<void> => {
if (event.native) {
const points = chart.getElementsAtEventForMode(
event.native,
'nearest',
{ intersect: true },
true,
);
const id = `${from}_button`;
const buttonElement = document.getElementById(id);
if (points.length !== 0) {
const firstPoint = points[0];
if (data.labels) {
const time = data?.labels[firstPoint.index] as Date;
if (buttonElement) {
buttonElement.style.display = 'block';
buttonElement.style.left = `${firstPoint.element.x}px`;
buttonElement.style.top = `${firstPoint.element.y}px`;
selectedTimeStamp.current = time.getTime();
}
}
} else if (buttonElement && buttonElement.style.display === 'block') {
buttonElement.style.display = 'none';
}
}
};
const onDragSelect = useCallback(
(start: number, end: number) => {
const startTimestamp = Math.trunc(start);
@ -162,9 +110,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
type="default"
size="small"
id="Service_button"
onClick={(): void => {
onTracePopupClick(selectedTimeStamp.current);
}}
onClick={onViewTracePopupClick(
servicename,
selectedTraceTags,
selectedTimeStamp,
)}
>
View Traces
</Button>
@ -173,7 +123,13 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
<GraphContainer>
<Graph
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onClickHandler(ChartEvent, activeElements, chart, data, 'Service');
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'Service',
);
}}
name="service_latency"
type="line"
@ -230,9 +186,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
type="default"
size="small"
id="Rate_button"
onClick={(): void => {
onTracePopupClick(selectedTimeStamp.current);
}}
onClick={onViewTracePopupClick(
servicename,
selectedTraceTags,
selectedTimeStamp,
)}
>
View Traces
</Button>
@ -243,7 +201,13 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
name="operations_per_sec"
fullViewOptions={false}
onClickHandler={(event, element, chart, data): void => {
onClickHandler(event, element, chart, data, 'Rate');
onGraphClickHandler(setSelectedTimeStamp)(
event,
element,
chart,
data,
'Rate',
);
}}
widget={operationPerSecWidget}
yAxisUnit="ops"
@ -260,7 +224,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
size="small"
id="Error_button"
onClick={(): void => {
onErrorTrackHandler(selectedTimeStamp.current);
onErrorTrackHandler(selectedTimeStamp);
}}
>
View Traces
@ -273,7 +237,13 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
name="error_percentage_%"
fullViewOptions={false}
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onClickHandler(ChartEvent, activeElements, chart, data, 'Error');
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'Error',
);
}}
widget={errorPercentageWidget}
yAxisUnit="%"

View File

@ -0,0 +1,75 @@
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { Tags } from 'types/reducer/trace';
export const dbSystemTags: Tags[] = [
{
Key: ['db.system.(string)'],
StringValues: [''],
NumberValues: [],
BoolValues: [],
Operator: 'Exists',
},
];
export function onViewTracePopupClick(
servicename: string | undefined,
selectedTraceTags: string,
timestamp: number,
): VoidFunction {
return (): void => {
const currentTime = timestamp;
const tPlusOne = timestamp + 1 * 60 * 1000;
const urlParams = new URLSearchParams();
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
history.replace(
`${
ROUTES.TRACE
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1`,
);
};
}
export function onGraphClickHandler(
setSelectedTimeStamp: React.Dispatch<React.SetStateAction<number>>,
) {
return async (
event: ChartEvent,
elements: ActiveElement[],
chart: Chart,
data: ChartData,
from: string,
): Promise<void> => {
if (event.native) {
const points = chart.getElementsAtEventForMode(
event.native,
'nearest',
{ intersect: true },
true,
);
const id = `${from}_button`;
const buttonElement = document.getElementById(id);
if (points.length !== 0) {
const firstPoint = points[0];
if (data.labels) {
const time = data?.labels[firstPoint.index] as Date;
if (buttonElement) {
buttonElement.style.display = 'block';
buttonElement.style.left = `${firstPoint.element.x}px`;
buttonElement.style.top = `${firstPoint.element.y}px`;
setSelectedTimeStamp(time.getTime());
}
}
} else if (buttonElement && buttonElement.style.display === 'block') {
buttonElement.style.display = 'none';
}
}
};
}

View File

@ -1,7 +1,7 @@
import { OperatorConversions } from 'constants/resourceAttributes';
import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types';
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
import { OperatorValues, Tags, TagsAPI } from 'types/reducer/trace';
import { OperatorValues, Tags } from 'types/reducer/trace';
/**
* resource_x_y -> x.y
@ -35,13 +35,9 @@ export const convertOperatorLabelToTraceOperator = (
export const convertRawQueriesToTraceSelectedTags = (
queries: IResourceAttributeQuery[],
keyType: 'string' | 'array' = 'string',
): Tags[] | TagsAPI[] =>
): Tags[] =>
queries.map((query) => ({
Key:
keyType === 'array'
? [convertMetricKeyToTrace(query.tagKey)]
: (convertMetricKeyToTrace(query.tagKey) as never),
Key: [convertMetricKeyToTrace(query.tagKey)],
Operator: convertOperatorLabelToTraceOperator(query.operator),
StringValues: query.tagValue,
NumberValues: [],

View File

@ -54,13 +54,6 @@ export interface Tags {
BoolValues: boolean[];
}
export interface TagsAPI {
Key: string;
Operator: OperatorValues;
StringValues: string[];
NumberValues: number[];
BoolValues: boolean[];
}
export type OperatorValues =
| 'NotIn'
| 'In'