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'; import FullView from 'container/GridGraphLayout/Graph/FullView'; import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond'; import { colors } from 'lib/getRandomColor'; import history from 'lib/history'; import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes'; import { escapeRegExp } from 'lodash-es'; import React, { useMemo, useRef } from 'react'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { AppState } from 'store/reducers'; import { PromQLWidgets } from 'types/api/dashboard/getAll'; import MetricReducer from 'types/reducer/metrics'; import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles'; import TopOperationsTable from '../TopOperationsTable'; import { Button } from './styles'; function Application({ getWidget }: DashboardProps): JSX.Element { const { servicename } = useParams<{ servicename?: string }>(); const selectedTimeStamp = useRef(0); const { topOperations, serviceOverview, resourceAttributePromQLQuery, resourceAttributeQueries, topLevelOperations, } = useSelector((state) => state.metrics); const operationsRegex = useMemo(() => { return encodeURIComponent( topLevelOperations.map((e) => escapeRegExp(e)).join('|'), ); }, [topLevelOperations]); const selectedTraceTags: string = JSON.stringify( convertRawQueriesToTraceSelectedTags(resourceAttributeQueries, 'array') || [], ); 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&spanAggregateOrder=ascend`, ); }; const onClickHandler = async ( event: ChartEvent, elements: ActiveElement[], chart: Chart, data: ChartData, from: string, ): Promise => { 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 onErrorTrackHandler = (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}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, ); }; return ( <> Latency { onClickHandler(ChartEvent, activeElements, chart, data, 'Service'); }} name="service_latency" type="line" data={{ datasets: [ { data: serviceOverview.map((e) => parseFloat(convertToNanoSecondsToSecond(e.p99)), ), borderColor: colors[0], label: 'p99 Latency', showLine: true, borderWidth: 1.5, spanGaps: true, pointRadius: 1.5, }, { data: serviceOverview.map((e) => parseFloat(convertToNanoSecondsToSecond(e.p95)), ), borderColor: colors[1], label: 'p95 Latency', showLine: true, borderWidth: 1.5, spanGaps: true, pointRadius: 1.5, }, { data: serviceOverview.map((e) => parseFloat(convertToNanoSecondsToSecond(e.p50)), ), borderColor: colors[2], label: 'p50 Latency', showLine: true, borderWidth: 1.5, spanGaps: true, pointRadius: 1.5, }, ], labels: serviceOverview.map((e) => { return new Date( parseFloat(convertToNanoSecondsToSecond(e.timestamp)), ); }), }} yAxisUnit="ms" /> Rate (ops/s) { onClickHandler(event, element, chart, data, 'Rate'); }} widget={getWidget([ { query: `sum(rate(signoz_latency_count{service_name="${servicename}", operation=~\`${operationsRegex}\`${resourceAttributePromQLQuery}}[5m]))`, legend: 'Operations', }, ])} yAxisUnit="ops" /> Error Percentage { onClickHandler(ChartEvent, activeElements, chart, data, 'Error'); }} widget={getWidget([ { query: `max(sum(rate(signoz_calls_total{service_name="${servicename}", operation=~\`${operationsRegex}\`, status_code="STATUS_CODE_ERROR"${resourceAttributePromQLQuery}}[5m]) OR rate(signoz_calls_total{service_name="${servicename}", operation=~\`${operationsRegex}\`, http_status_code=~"5.."${resourceAttributePromQLQuery}}[5m]))*100/sum(rate(signoz_calls_total{service_name="${servicename}", operation=~\`${operationsRegex}\`${resourceAttributePromQLQuery}}[5m]))) < 1000 OR vector(0)`, legend: 'Error Percentage', }, ])} yAxisUnit="%" /> ); } interface DashboardProps { getWidget: (query: PromQLWidgets['query']) => PromQLWidgets; } export default Application;