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/index.metricsBuilder'; import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond'; import { colors } from 'lib/getRandomColor'; import history from 'lib/history'; import { convertRawQueriesToTraceSelectedTags, resourceAttributesToTagFilterItems, } from 'lib/resourceAttributes'; import React, { useCallback, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { UpdateTimeInterval } from 'store/actions'; import { AppState } from 'store/reducers'; import { Widgets } from 'types/api/dashboard/getAll'; import MetricReducer from 'types/reducer/metrics'; import { errorPercentage, operationPerSec, } from '../MetricsPageQueries/OverviewQueries'; 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, setSelectedTimeStamp] = useState(0); const handleSetTimeStamp = useCallback((selectTime: number) => { setSelectedTimeStamp(selectTime); }, []); const dispatch = useDispatch(); const handleGraphClick = useCallback( (type: string): ClickHandlerType => ( ChartEvent: ChartEvent, activeElements: ActiveElement[], chart: Chart, data: ChartData, ): void => { onGraphClickHandler(handleSetTimeStamp)( ChartEvent, activeElements, chart, data, type, ); }, [handleSetTimeStamp], ); const { topOperations, serviceOverview, resourceAttributeQueries, topLevelOperations, } = useSelector((state) => state.metrics); const selectedTraceTags: string = JSON.stringify( convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [], ); const tagFilterItems = useMemo( () => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [], [resourceAttributeQueries], ); const operationPerSecWidget = useMemo( () => getWidgetQueryBuilder({ queryType: 1, promQL: [], metricsBuilder: operationPerSec({ servicename, tagFilterItems, topLevelOperations, }), clickHouse: [], }), [getWidgetQueryBuilder, servicename, topLevelOperations, tagFilterItems], ); const errorPercentageWidget = useMemo( () => getWidgetQueryBuilder({ queryType: 1, promQL: [], metricsBuilder: errorPercentage({ servicename, tagFilterItems, topLevelOperations, }), clickHouse: [], }), [servicename, topLevelOperations, tagFilterItems, getWidgetQueryBuilder], ); const onDragSelect = useCallback( (start: number, end: number) => { const startTimestamp = Math.trunc(start); const endTimestamp = Math.trunc(end); if (startTimestamp !== endTimestamp) { dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp])); } }, [dispatch], ); const onErrorTrackHandler = (timestamp: number): void => { const currentTime = timestamp; const tPlusOne = timestamp + 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`, ); }; 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( () => [ { 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( () => ({ datasets: dataSets, labels: serviceOverview.map( (e) => new Date(parseFloat(convertToNanoSecondsToSecond(e.timestamp))), ), }), [serviceOverview, dataSets], ); return ( <> Latency Rate (ops/s) Error Percentage ); } interface DashboardProps { getWidgetQueryBuilder: (query: Widgets['query']) => Widgets; } type ClickHandlerType = ( ChartEvent: ChartEvent, activeElements: ActiveElement[], chart: Chart, data: ChartData, type?: string, ) => void; export default Application;