/* eslint-disable sonarjs/no-duplicate-string */ import { Color } from '@signozhq/design-tokens'; import { Progress, Tag, Tooltip } from 'antd'; import { ColumnType } from 'antd/es/table'; import { FiltersType, IQuickFiltersConfig, } from 'components/QuickFilters/types'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { GraphClickMetaData } from 'container/GridCardLayout/useNavigateToExplorerPages'; import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; import dayjs from 'dayjs'; import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; import { cloneDeep } from 'lodash-es'; import { ArrowUpDown, ChevronDown, ChevronRight, Info } from 'lucide-react'; import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil'; import { ReactNode } from 'react'; import { Widgets } from 'types/api/dashboard/getAll'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { BaseAutocompleteData, DataTypes, } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { IBuilderQuery, OrderByPayload, TagFilterItem, } from 'types/api/queryBuilder/queryBuilderData'; import { QueryData } from 'types/api/widgets/getQuery'; import { EQueryType } from 'types/common/dashboard'; import { DataSource } from 'types/common/queryBuilder'; import { v4 } from 'uuid'; import { SPAN_ATTRIBUTES } from './Explorer/Domains/DomainDetails/constants'; export const ApiMonitoringQuickFiltersConfig: IQuickFiltersConfig[] = [ { type: FiltersType.CHECKBOX, title: 'Environment', attributeKey: { key: 'deployment.environment', dataType: DataTypes.String, type: 'resource', isColumn: false, isJSON: false, }, dataSource: DataSource.TRACES, defaultOpen: true, }, { type: FiltersType.CHECKBOX, title: 'Service Name', attributeKey: { key: 'service.name', dataType: DataTypes.String, type: 'resource', isColumn: true, isJSON: false, }, dataSource: DataSource.TRACES, defaultOpen: true, }, { type: FiltersType.CHECKBOX, title: 'RPC Method', attributeKey: { key: 'rpc.method', dataType: DataTypes.String, type: 'tag', isColumn: true, isJSON: false, }, dataSource: DataSource.TRACES, defaultOpen: true, }, ]; export const getLastUsedRelativeTime = (lastRefresh: number): string => { const currentTime = dayjs(); const secondsDiff = currentTime.diff(lastRefresh, 'seconds'); const minutedDiff = currentTime.diff(lastRefresh, 'minutes'); const hoursDiff = currentTime.diff(lastRefresh, 'hours'); const daysDiff = currentTime.diff(lastRefresh, 'days'); const monthsDiff = currentTime.diff(lastRefresh, 'months'); if (monthsDiff > 0) { return `${monthsDiff} ${monthsDiff === 1 ? 'month' : 'months'} ago`; } if (daysDiff > 0) { return `${daysDiff} ${daysDiff === 1 ? 'day' : 'days'} ago`; } if (hoursDiff > 0) { return `${hoursDiff}h ago`; } if (minutedDiff > 0) { return `${minutedDiff}m ago`; } return `${secondsDiff}s ago`; }; // Rename this to a proper name export const columnsConfig: ColumnType[] = [ { title:
Domain
, dataIndex: 'domainName', key: 'domainName', width: '23.7%', ellipsis: true, sorter: false, className: 'column column-domain-name', align: 'left', render: (domainName: string): React.ReactNode => (
{domainName}
), }, { title:
Endpoints in use
, dataIndex: 'endpointCount', key: 'endpointCount', width: '14.2%', ellipsis: true, sorter: (rowA: APIDomainsRowData, rowB: APIDomainsRowData): number => { const endpointA = rowA.endpointCount === '-' || rowA.endpointCount === 'n/a' ? '' : rowA.endpointCount; const endpointB = rowB.endpointCount === '-' || rowB.endpointCount === 'n/a' ? '' : rowB.endpointCount; // Handle cases where one or both values are empty if (!endpointA && !endpointB) return 0; if (!endpointA) return 1; if (!endpointB) return -1; return Number(endpointA) - Number(endpointB); }, align: 'right', className: `column`, }, { title:
Last used
, dataIndex: 'lastUsed', key: 'lastUsed', width: '14.2%', align: 'right', className: `column`, sorter: (rowA: APIDomainsRowData, rowB: APIDomainsRowData): number => { const dateA = rowA.lastUsed === '-' || rowA.lastUsed === 'n/a' ? new Date(0).toISOString() : rowA.lastUsed; const dateB = rowB.lastUsed === '-' || rowB.lastUsed === 'n/a' ? new Date(0).toISOString() : rowB.lastUsed; return new Date(dateB).getTime() - new Date(dateA).getTime(); }, render: (lastUsed: string): string => lastUsed === 'n/a' || lastUsed === '-' ? '-' : getLastUsedRelativeTime(new Date(lastUsed).getTime()), }, { title: (
Rate ops/s
), dataIndex: 'rate', key: 'rate', width: '14.2%', sorter: (rowA: APIDomainsRowData, rowB: APIDomainsRowData): number => { const rateA = rowA.rate === '-' || rowA.rate === 'n/a' ? 0 : rowA.rate; const rateB = rowB.rate === '-' || rowB.rate === 'n/a' ? 0 : rowB.rate; return Number(rateA) - Number(rateB); }, align: 'right', className: `column`, }, { title: (
Error %
), dataIndex: 'errorRate', key: 'errorRate', width: '14.2%', sorter: (rowA: APIDomainsRowData, rowB: APIDomainsRowData): number => { const errorRateA = rowA.errorRate === '-' || rowA.errorRate === 'n/a' ? 0 : rowA.errorRate; const errorRateB = rowB.errorRate === '-' || rowB.errorRate === 'n/a' ? 0 : rowB.errorRate; return Number(errorRateA) - Number(errorRateB); }, align: 'right', className: `column`, render: (errorRate: number | string): React.ReactNode => { const errorRateValue = errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate; return ( { const errorRatePercent = Number((errorRateValue as number).toFixed(2)); if (errorRatePercent >= 90) return Color.BG_SAKURA_500; if (errorRatePercent >= 60) return Color.BG_AMBER_500; return Color.BG_FOREST_500; })()} className="progress-bar error-rate" /> ); }, }, { title: (
Avg. Latency ms
), dataIndex: 'latency', key: 'latency', width: '14.2%', sorter: (rowA: APIDomainsRowData, rowB: APIDomainsRowData): number => { const latencyA = rowA.latency === '-' || rowA.latency === 'n/a' ? 0 : rowA.latency; const latencyB = rowB.latency === '-' || rowB.latency === 'n/a' ? 0 : rowB.latency; return Number(latencyA) - Number(latencyB); }, align: 'right', className: `column`, }, ]; // Rename this to a proper name export const hardcodedAttributeKeys: BaseAutocompleteData[] = [ { key: 'deployment.environment', dataType: DataTypes.String, type: 'resource', isColumn: false, isJSON: false, }, { key: 'service.name', dataType: DataTypes.String, type: 'resource', isColumn: true, isJSON: false, }, { key: 'rpc.method', dataType: DataTypes.String, type: 'tag', isColumn: true, isJSON: false, }, ]; const domainNameKey = SPAN_ATTRIBUTES.SERVER_NAME; interface APIMonitoringResponseRow { data: { endpoints: number | string; error_rate: number | string; lastseen: number | string; [domainNameKey]: string; p99: number | string; rps: number | string; }; } interface EndPointsResponseRow { data: { [key: string]: string | number | undefined; }; } export interface APIDomainsRowData { key: string; domainName: string; endpointCount: number | string; rate: number | string; errorRate: number | string; latency: number | string; lastUsed: string; } // Rename this to a proper name export const formatDataForTable = ( data: APIMonitoringResponseRow[], ): APIDomainsRowData[] => data?.map((domain) => ({ key: v4(), domainName: domain?.data[domainNameKey] || '-', endpointCount: domain?.data?.endpoints === 'n/a' || domain?.data?.endpoints === undefined ? 0 : domain?.data?.endpoints, rate: domain?.data?.rps === 'n/a' || domain?.data?.rps === undefined ? '-' : domain?.data?.rps, errorRate: domain?.data?.error_rate === 'n/a' || domain?.data?.error_rate === undefined ? 0 : domain?.data?.error_rate, latency: domain?.data?.p99 === 'n/a' || domain?.data?.p99 === undefined ? '-' : Math.round(Number(domain?.data?.p99) / 1000000), // Convert from nanoseconds to milliseconds lastUsed: domain?.data?.lastseen === 'n/a' || domain?.data?.lastseen === undefined ? '-' : new Date( Math.floor(Number(domain?.data?.lastseen) / 1000000), ).toISOString(), // Convert from nanoseconds to milliseconds })); export const getDomainMetricsQueryPayload = ( domainName: string, start: number, end: number, filters: IBuilderQuery['filters'], ): GetQueryResultsProps[] => [ { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TABLE, query: { builder: { queryData: [ { dataSource: DataSource.TRACES, queryName: 'A', aggregateOperator: 'count', aggregateAttribute: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.URL_PATH, type: 'tag', }, timeAggregation: 'rate', spaceAggregation: 'sum', functions: [], filters: { items: [ { id: '4c57937c', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, ...filters.items, ], op: 'AND', }, expression: 'A', disabled: false, stepInterval: 60, having: [], limit: null, orderBy: [], groupBy: [], legend: '', reduceTo: 'avg', }, { dataSource: DataSource.TRACES, queryName: 'B', aggregateOperator: 'p99', aggregateAttribute: { dataType: DataTypes.Float64, isColumn: true, isJSON: false, key: 'duration_nano', type: '', }, timeAggregation: 'p99', spaceAggregation: 'sum', functions: [], filters: { items: [ { id: '2cf675cd', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, ...filters.items, ], op: 'AND', }, expression: 'B', disabled: false, stepInterval: 60, having: [], limit: null, orderBy: [], groupBy: [], legend: '', reduceTo: 'avg', }, { dataSource: DataSource.TRACES, queryName: 'C', aggregateOperator: 'count', aggregateAttribute: { dataType: DataTypes.String, id: '------false', isColumn: false, key: '', type: '', }, timeAggregation: 'count', spaceAggregation: 'sum', functions: [], filters: { items: [ { id: '3db0f605', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '6096f745', key: { dataType: DataTypes.bool, isColumn: true, isJSON: false, key: 'has_error', type: '', }, op: '=', value: 'true', }, ...filters.items, ], op: 'AND', }, expression: 'C', disabled: true, stepInterval: 60, having: [], limit: null, orderBy: [], groupBy: [], legend: '', reduceTo: 'avg', }, { dataSource: DataSource.TRACES, queryName: 'D', aggregateOperator: 'max', aggregateAttribute: { dataType: DataTypes.String, id: 'timestamp------false', isColumn: false, key: 'timestamp', type: '', }, timeAggregation: 'max', spaceAggregation: 'sum', functions: [], filters: { items: [ { id: '8ff8dea1', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, ...filters.items, ], op: 'AND', }, expression: 'D', disabled: false, stepInterval: 60, having: [], limit: null, orderBy: [], groupBy: [], legend: '', reduceTo: 'avg', }, ], queryFormulas: [ { queryName: 'F1', expression: '(C/A)*100', disabled: false, legend: '', }, ], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, formatForWeb: true, start, end, step: 60, }, ]; export interface DomainMetricsData { endpointCount: number | string; latency: number | string; errorRate: number | string; lastUsed: number | string; } export interface DomainMetricsResponseRow { data: { A: number | string; B: number | string; D: number | string; F1: number | string; }; } export const formatDomainMetricsDataForTable = ( row: DomainMetricsResponseRow | undefined, ): DomainMetricsData => { if (!row) { return { endpointCount: '-', latency: '-', errorRate: 0, lastUsed: '-', }; } return { endpointCount: row.data.A === 'n/a' || !row.data.A ? '-' : Number(row.data.A), latency: row.data.B === 'n/a' || row.data.B === undefined ? '-' : Math.round(Number(row.data.B) / 1000000), errorRate: row.data.F1 === 'n/a' || !row.data.F1 ? 0 : Number(row.data.F1), lastUsed: row.data.D === 'n/a' || !row.data.D ? '-' : getLastUsedRelativeTime(Math.floor(Number(row.data.D) / 1000000)), }; }; // Rename this to a proper name const defaultGroupBy = [ { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.URL_PATH, type: 'tag', }, // { // key: SPAN_ATTRIBUTES.SERVER_PORT, // dataType: DataTypes.Float64, // type: 'tag', // isColumn: false, // isJSON: false, // }, ]; export const getEndPointsQueryPayload = ( groupBy: BaseAutocompleteData[], domainName: string, start: number, end: number, ): GetQueryResultsProps[] => { const isGroupedByAttribute = groupBy.length > 0; return [ { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TABLE, query: { builder: { queryData: [ { aggregateAttribute: { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'span_id', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: 'ec316e57', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ], op: 'AND', }, functions: [], groupBy: isGroupedByAttribute ? groupBy : defaultGroupBy, having: [], legend: '', limit: 1000, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'count', }, { aggregateAttribute: { dataType: DataTypes.Float64, isColumn: true, isJSON: false, key: 'duration_nano', type: '', }, aggregateOperator: 'p99', dataSource: DataSource.TRACES, disabled: false, expression: 'B', filters: { items: [ { id: '46d57857', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ], op: 'AND', }, functions: [], groupBy: isGroupedByAttribute ? groupBy : defaultGroupBy, having: [], legend: '', limit: 1000, orderBy: [], queryName: 'B', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'p99', }, { aggregateAttribute: { dataType: DataTypes.String, id: 'timestamp------false', isColumn: false, key: 'timestamp', type: '', }, aggregateOperator: 'max', dataSource: DataSource.TRACES, disabled: false, expression: 'C', filters: { items: [ { id: '4a237616', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ], op: 'AND', }, functions: [], groupBy: isGroupedByAttribute ? groupBy : defaultGroupBy, having: [], legend: '', limit: 1000, orderBy: [], queryName: 'C', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'max', }, { aggregateAttribute: { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'span_id', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: true, expression: 'D', filters: { items: [ { id: 'f162de1e', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '3df0ac1d', key: { dataType: DataTypes.bool, isColumn: true, isJSON: false, key: 'has_error', type: '', }, op: '=', value: 'true', }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ], op: 'AND', }, functions: [], groupBy: isGroupedByAttribute ? groupBy : defaultGroupBy, having: [], legend: '', limit: 1000, orderBy: [], queryName: 'D', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'count', }, ], queryFormulas: [ { queryName: 'F1', expression: '(D/A)*100', disabled: false, legend: 'error percentage', }, ], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, formatForWeb: true, start, end, step: 60, }, ]; }; export const getTopErrorsQueryPayload = ( domainName: string, start: number, end: number, filters: IBuilderQuery['filters'], ): GetQueryResultsProps[] => [ { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TABLE, query: { builder: { queryData: [ { dataSource: DataSource.TRACES, queryName: 'A', aggregateOperator: 'count', aggregateAttribute: { id: '------false', dataType: DataTypes.String, key: '', isColumn: false, type: '', isJSON: false, }, timeAggregation: 'rate', spaceAggregation: 'sum', functions: [], filters: { op: 'AND', items: [ { id: '04da97bd', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, { id: 'b1af6bdb', key: { key: SPAN_ATTRIBUTES.URL_PATH, dataType: DataTypes.String, type: 'tag', isColumn: false, isJSON: false, }, op: 'exists', value: '', }, { id: '75d65388', key: { key: 'status_message', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: 'exists', value: '', }, { id: '4872bf91', key: { key: SPAN_ATTRIBUTES.SERVER_NAME, dataType: DataTypes.String, type: 'tag', isColumn: false, isJSON: false, }, op: '=', value: domainName, }, { id: 'ab4c885d', key: { key: 'has_error', dataType: DataTypes.bool, type: '', isColumn: true, isJSON: false, }, op: '=', value: true, }, ...filters.items, ], }, expression: 'A', disabled: false, stepInterval: 60, having: [], limit: 10, orderBy: [ { columnName: 'timestamp', order: 'desc', }, ], groupBy: [ { key: SPAN_ATTRIBUTES.URL_PATH, dataType: DataTypes.String, type: 'tag', isColumn: false, isJSON: false, }, { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'response_status_code', type: '', id: 'response_status_code--string----true', }, { key: 'status_message', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, ], legend: '', reduceTo: 'avg', }, ], queryFormulas: [], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, start, end, step: 240, }, ]; export interface EndPointsTableRowData { key: string; endpointName: string; callCount: number | string; latency: number | string; errorRate: number | string; lastUsed: string | number; groupedByMeta?: Record; } export const extractPortAndEndpoint = ( url: string, ): { port: string; endpoint: string } => { try { // Create a URL object to parse the URL const parsedUrl = new URL(url); // Extract the port (will be empty string if not specified) const port = parsedUrl.port || '-'; // Extract the pathname (endpoint) + query params const endpoint = parsedUrl.pathname + parsedUrl.search; return { port, endpoint }; } catch (error) { // If URL parsing fails, return default values return { port: '-', endpoint: url }; } }; export const getEndPointsColumnsConfig = ( isGroupedByAttribute: boolean, expandedRowKeys: React.Key[], // eslint-disable-next-line sonarjs/cognitive-complexity ): ColumnType[] => [ { title: (
{isGroupedByAttribute ? 'Endpoint group' : 'Endpoint'}
), dataIndex: 'endpointName', key: 'endpointName', width: 180, ellipsis: true, sorter: false, className: 'column', render: (text: string, record: EndPointsTableRowData): React.ReactNode => { const endPointName = isGroupedByAttribute ? text : extractPortAndEndpoint(record.endpointName).endpoint; return (
{((): React.ReactNode => { if (!isGroupedByAttribute) return null; return expandedRowKeys.includes(record.key) ? ( ) : ( ); })()} {isGroupedByAttribute ? text.split(',').map((value) => ( {value === '' ? '' : value} )) : endPointName}
); }, }, { title:
Port
, dataIndex: 'port', key: 'port', width: 180, ellipsis: true, sorter: false, align: 'right', className: `column`, }, { title: (
Num of calls
), dataIndex: 'callCount', key: 'callCount', width: 180, ellipsis: true, align: 'right', className: `column`, }, { title: (
Error %
), dataIndex: 'errorRate', key: 'errorRate', width: 120, sorter: true, align: 'right', className: `column`, render: ( errorRate: number | string, // eslint-disable-next-line sonarjs/no-identical-functions ): React.ReactNode => ( { const errorRatePercent = Number((errorRate as number).toFixed(1)); if (errorRatePercent >= 90) return Color.BG_SAKURA_500; if (errorRatePercent >= 60) return Color.BG_AMBER_500; return Color.BG_FOREST_500; })()} className="progress-bar error-rate" /> ), }, { title: (
Latency ms
), dataIndex: 'latency', key: 'latency', width: 120, sorter: true, align: 'right', className: `column`, }, { title:
Last used
, dataIndex: 'lastUsed', key: 'lastUsed', width: 120, sorter: true, align: 'right', className: `column`, // eslint-disable-next-line sonarjs/no-identical-functions render: (lastUsed: string): string => lastUsed === 'n/a' || lastUsed === '-' ? '-' : getLastUsedRelativeTime(new Date(lastUsed).getTime()), }, ]; export const formatEndPointsDataForTable = ( data: EndPointsResponseRow[] | undefined, groupBy: BaseAutocompleteData[], orderBy?: OrderByPayload | null, // eslint-disable-next-line sonarjs/cognitive-complexity ): EndPointsTableRowData[] => { if (!data) return []; const isGroupedByAttribute = groupBy.length > 0; let formattedData: EndPointsTableRowData[] = []; if (!isGroupedByAttribute) { formattedData = data?.map((endpoint) => { const { port } = extractPortAndEndpoint( (endpoint.data[SPAN_ATTRIBUTES.URL_PATH] as string) || '', ); return { key: v4(), endpointName: (endpoint.data[SPAN_ATTRIBUTES.URL_PATH] as string) || '-', port, callCount: endpoint.data.A === 'n/a' || endpoint.data.A === undefined ? '-' : endpoint.data.A, latency: endpoint.data.B === 'n/a' || endpoint.data.B === undefined ? '-' : Math.round(Number(endpoint.data.B) / 1000000), // Convert from nanoseconds to milliseconds lastUsed: endpoint.data.C === 'n/a' || endpoint.data.C === undefined ? '-' : new Date(Math.floor(Number(endpoint.data.C) / 1000000)).toISOString(), // Convert from nanoseconds to milliseconds errorRate: endpoint.data.F1 === undefined || endpoint.data.F1 === 'n/a' ? 0 : Number(endpoint.data.F1), }; }); } else { const groupedByAttributeData = groupBy.map((attribute) => attribute.key); formattedData = data?.map((endpoint) => { const newEndpointName = groupedByAttributeData .map((attribute) => endpoint.data[attribute]) .join(','); return { key: v4(), endpointName: newEndpointName, callCount: endpoint.data.A === 'n/a' || endpoint.data.A === undefined ? '-' : endpoint.data.A, latency: endpoint.data.B === 'n/a' || endpoint.data.B === undefined ? '-' : Math.round(Number(endpoint.data.B) / 1000000), // Convert from nanoseconds to milliseconds lastUsed: endpoint.data.C === 'n/a' || endpoint.data.C === undefined ? '-' : getLastUsedRelativeTime(Math.floor(Number(endpoint.data.C) / 1000000)), // Convert from nanoseconds to milliseconds errorRate: endpoint.data.D === 'n/a' || endpoint.data.D === undefined ? 0 : Number(endpoint.data.D), groupedByMeta: groupedByAttributeData.reduce((acc, attribute) => { acc[attribute] = endpoint.data[attribute] || ''; return acc; }, {} as Record), }; }); } // Apply sorting if orderBy is provided if (orderBy) { formattedData.sort((a, b) => { let valueA: number | string = a[ orderBy.columnName as keyof EndPointsTableRowData ] as number | string; let valueB: number | string = b[ orderBy.columnName as keyof EndPointsTableRowData ] as number | string; // Handle special cases for each column type if ( orderBy.columnName === 'callCount' || orderBy.columnName === 'latency' || orderBy.columnName === 'errorRate' ) { valueA = valueA === '-' || valueA === 'n/a' ? 0 : Number(valueA); valueB = valueB === '-' || valueB === 'n/a' ? 0 : Number(valueB); } else if (orderBy.columnName === 'lastUsed') { // confirm once the implication of this valueA = valueA === '-' || valueA === 'n/a' ? new Date(0).getTime() : new Date(valueA as string).getTime(); valueB = valueB === '-' || valueB === 'n/a' ? new Date(0).getTime() : new Date(valueB as string).getTime(); } // Apply sort direction if (orderBy.order === 'asc') { return valueA > valueB ? 1 : -1; } return valueA < valueB ? 1 : -1; }); } return formattedData; }; export interface TopErrorsResponseRow { metric: { [SPAN_ATTRIBUTES.URL_PATH]: string; [SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE]: string; status_message: string; }; values: [number, string][]; queryName: string; legend: string; } export interface TopErrorsTableRowData { key: string; endpointName: string; statusCode: string; statusMessage: string; count: number | string; } export const formatTopErrorsDataForTable = ( data: TopErrorsResponseRow[] | undefined, ): TopErrorsTableRowData[] => { if (!data) return []; return data.map((row) => ({ key: v4(), endpointName: row.metric[SPAN_ATTRIBUTES.URL_PATH] === 'n/a' || row.metric[SPAN_ATTRIBUTES.URL_PATH] === undefined ? '-' : row.metric[SPAN_ATTRIBUTES.URL_PATH], statusCode: row.metric[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE] === 'n/a' || row.metric[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE] === undefined ? '-' : row.metric[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE], statusMessage: row.metric.status_message === 'n/a' || row.metric.status_message === undefined ? '-' : row.metric.status_message, count: row.values && row.values[0] && row.values[0][1] !== undefined && row.values[0][1] !== 'n/a' ? row.values[0][1] : '-', })); }; export const getTopErrorsCoRelationQueryFilters = ( domainName: string, endPointName: string, statusCode: string, ): IBuilderQuery['filters'] => ({ items: [ { id: 'ea16470b', key: { key: 'http.url', dataType: DataTypes.String, type: 'tag', isColumn: false, isJSON: false, id: 'http.url--string--tag--false', }, op: '=', value: endPointName, }, { id: 'b0ef3799', key: { key: 'has_error', dataType: DataTypes.bool, type: '', isColumn: false, isJSON: false, }, op: '=', value: 'true', }, { id: 'e8a043b7', key: { key: 'net.peer.name', dataType: DataTypes.String, type: '', isColumn: false, isJSON: false, }, op: '=', value: domainName, }, { id: 'f6891e27', key: { key: 'status_code', dataType: DataTypes.Float64, type: '', isColumn: true, isJSON: false, id: 'status_code--float64----true', }, op: '=', value: statusCode, }, ], op: 'AND', }); export const getTopErrorsColumnsConfig = (): ColumnType[] => [ { title:
Endpoint
, dataIndex: 'endpointName', key: 'endpointName', width: 180, ellipsis: true, sorter: false, className: 'column', render: (text: string, record: TopErrorsTableRowData): React.ReactNode => { const { endpoint } = extractPortAndEndpoint(record.endpointName); return (
{endpoint}
); }, }, { title:
Status code
, dataIndex: 'statusCode', key: 'statusCode', width: 180, ellipsis: true, sorter: false, align: 'right', className: `column`, }, { title:
Status message
, dataIndex: 'statusMessage', key: 'statusMessage', width: 180, ellipsis: true, align: 'right', className: `column`, }, { title:
Count
, dataIndex: 'count', key: 'count', width: 120, sorter: false, align: 'right', className: `column`, }, ]; export const createFiltersForSelectedRowData = ( selectedRowData: EndPointsTableRowData, currentFilters?: IBuilderQuery['filters'], ): IBuilderQuery['filters'] => { const baseFilters: IBuilderQuery['filters'] = { items: [...(currentFilters?.items || [])], op: 'and', }; if (!selectedRowData) return baseFilters; const { groupedByMeta = {} } = selectedRowData; // Replace for...of with Object.keys().map() baseFilters.items.push( ...Object.keys(groupedByMeta).map((key) => ({ key: { key, type: null, }, op: '=', value: groupedByMeta[key] || '', id: key, })), ); return baseFilters; }; // First query payload for endpoint metrics // Second query payload for endpoint status code table // Third query payload for endpoint dropdown selection // Fourth query payload for endpoint dependant services // Fifth query payload for endpoint response status count bar chart // Sixth query payload for endpoint response status code latency bar chart export const getEndPointDetailsQueryPayload = ( domainName: string, start: number, end: number, filters: IBuilderQuery['filters'], ): GetQueryResultsProps[] => [ { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TABLE, query: { builder: { queryData: [ { aggregateAttribute: { dataType: DataTypes.String, id: '------false', isColumn: false, isJSON: false, key: '', type: '', }, aggregateOperator: 'rate', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: '874562e1', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [], having: [], legend: 'Rate', limit: null, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'rate', }, { aggregateAttribute: { dataType: DataTypes.Float64, isColumn: true, isJSON: false, key: 'duration_nano', type: '', }, aggregateOperator: 'p99', dataSource: DataSource.TRACES, disabled: false, expression: 'B', filters: { items: [ { id: '0c5564e0', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [], having: [], legend: 'P99', limit: null, orderBy: [], queryName: 'B', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'p99', }, { aggregateAttribute: { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'span_id', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: true, expression: 'C', filters: { items: [ { id: '0d656701', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '83ef9a1b', key: { dataType: DataTypes.bool, isColumn: true, isJSON: false, key: 'has_error', type: '', }, op: '=', value: 'true', }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [], having: [], legend: '', limit: null, orderBy: [], queryName: 'C', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'count', }, { aggregateAttribute: { dataType: DataTypes.String, id: 'timestamp------false', isColumn: false, key: 'timestamp', type: '', }, aggregateOperator: 'max', dataSource: DataSource.TRACES, disabled: false, expression: 'D', filters: { items: [ { id: '918f5b99', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [], having: [], legend: 'Last seen', limit: null, orderBy: [], queryName: 'D', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'max', }, { aggregateAttribute: { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'span_id', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: true, expression: 'E', filters: { items: [ { id: 'b355d1aa', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [], having: [], legend: 'total', limit: null, orderBy: [], queryName: 'E', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'count', }, ], queryFormulas: [ { queryName: 'F1', expression: '(C/E)*100', disabled: false, legend: 'error percentage', }, ], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, formatForWeb: true, start, end, step: 60, }, { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TABLE, query: { builder: { queryData: [ { aggregateAttribute: { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'span_id', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: '23450eb8', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [ { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'response_status_code', type: '', }, ], having: [], legend: 'number of calls', limit: null, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'count', }, { aggregateAttribute: { dataType: DataTypes.Float64, isColumn: true, isJSON: false, key: 'duration_nano', type: '', }, aggregateOperator: 'p99', dataSource: DataSource.TRACES, disabled: false, expression: 'B', filters: { items: [ { id: '2687dc18', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [ { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'response_status_code', type: '', }, ], having: [], legend: 'p99 latency', limit: null, orderBy: [], queryName: 'B', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'p99', }, { dataSource: DataSource.TRACES, queryName: 'C', aggregateOperator: 'rate', aggregateAttribute: { dataType: DataTypes.String, id: '------false', isColumn: false, key: '', type: '', }, timeAggregation: 'rate', spaceAggregation: 'sum', functions: [], filters: { items: [ { id: '334840be', key: { dataType: DataTypes.String, id: 'net.peer.name--string--tag--false', isColumn: false, isJSON: false, key: 'net.peer.name', type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, expression: 'C', disabled: false, stepInterval: 60, having: [], limit: null, orderBy: [], groupBy: [ { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'response_status_code', type: '', id: 'response_status_code--string----true', }, ], legend: 'rate', reduceTo: 'avg', }, ], queryFormulas: [], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, formatForWeb: true, start, end, step: 60, }, { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TABLE, query: { builder: { queryData: [ { dataSource: DataSource.TRACES, queryName: 'A', aggregateOperator: 'count', aggregateAttribute: { dataType: DataTypes.String, key: '', isColumn: false, type: '', }, timeAggregation: 'count', spaceAggregation: 'sum', functions: [], filters: { items: [ { id: '3db61dd6', key: { key: SPAN_ATTRIBUTES.SERVER_NAME, dataType: DataTypes.String, type: 'tag', isColumn: false, isJSON: false, }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, expression: 'A', disabled: false, stepInterval: 60, having: [], legend: '', limit: null, orderBy: [], groupBy: [ { key: SPAN_ATTRIBUTES.URL_PATH, dataType: DataTypes.String, type: 'tag', isColumn: false, isJSON: false, }, ], reduceTo: 'avg', }, ], queryFormulas: [], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, formatForWeb: true, start, end, step: 60, }, { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TABLE, query: { builder: { queryData: [ { aggregateAttribute: { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'span_id', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: 'b78ff216', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [ { dataType: DataTypes.String, id: 'service.name--string--resource--true', isColumn: true, isJSON: false, key: 'service.name', type: 'resource', }, ], having: [], legend: 'count', limit: null, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'count', }, { aggregateAttribute: { dataType: DataTypes.Float64, isColumn: true, isJSON: false, key: 'duration_nano', type: '', }, aggregateOperator: 'p99', dataSource: DataSource.TRACES, disabled: false, expression: 'B', filters: { items: [ { id: 'a9024472', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [ { dataType: DataTypes.String, id: 'service.name--string--resource--true', isColumn: true, isJSON: false, key: 'service.name', type: 'resource', }, ], having: [], legend: 'p99 latency', limit: null, orderBy: [], queryName: 'B', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'p99', }, { aggregateAttribute: { dataType: DataTypes.String, id: '------false', isColumn: false, key: '', type: '', }, aggregateOperator: 'rate', dataSource: DataSource.TRACES, disabled: false, expression: 'C', filters: { items: [ { id: '1b6c062d', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [ { dataType: DataTypes.String, id: 'service.name--string--resource--true', isColumn: true, isJSON: false, key: 'service.name', type: 'resource', }, ], having: [], legend: 'request per second', limit: null, orderBy: [], queryName: 'C', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'rate', }, { aggregateAttribute: { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'span_id', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: true, expression: 'D', filters: { items: [ { id: 'd14792a8', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '3212bf1a', key: { dataType: DataTypes.bool, isColumn: true, isJSON: false, key: 'has_error', type: '', }, op: '=', value: 'true', }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [ { dataType: DataTypes.String, id: 'service.name--string--resource--true', isColumn: true, isJSON: false, key: 'service.name', type: 'resource', }, ], having: [], legend: 'count', limit: null, orderBy: [], queryName: 'D', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'count', }, ], queryFormulas: [ { queryName: 'F1', expression: '(D/A)*100', disabled: false, legend: 'error percentage', }, ], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, formatForWeb: true, start, end, step: 60, }, { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TIME_SERIES, query: { builder: { queryData: [ { aggregateAttribute: { dataType: DataTypes.String, id: '------false', isColumn: false, key: '', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: 'c6724407', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [ { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'response_status_code', type: '', }, ], having: [], legend: '', limit: null, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'rate', }, ], queryFormulas: [], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, formatForWeb: false, start, end, step: 60, }, { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TIME_SERIES, query: { builder: { queryData: [ { aggregateAttribute: { dataType: DataTypes.Float64, isColumn: true, isJSON: false, key: 'duration_nano', type: '', }, aggregateOperator: 'p99', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: 'aae93366', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [ { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'response_status_code', type: '', }, ], having: [], legend: '', limit: null, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'p99', }, ], queryFormulas: [], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, formatForWeb: false, start, end, step: 60, }, ]; export const getEndPointZeroStateQueryPayload = ( domainName: string, start: number, end: number, ): GetQueryResultsProps[] => [ { selectedTime: 'GLOBAL_TIME', graphType: PANEL_TYPES.TABLE, query: { builder: { queryData: [ { dataSource: DataSource.TRACES, queryName: 'A', aggregateOperator: 'count', aggregateAttribute: { dataType: DataTypes.String, key: '', isColumn: false, type: '', }, timeAggregation: 'count', spaceAggregation: 'sum', functions: [], filters: { items: [ { id: '3db61dd6', key: { key: SPAN_ATTRIBUTES.SERVER_NAME, dataType: DataTypes.String, type: 'tag', isColumn: false, isJSON: false, }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ], op: 'AND', }, expression: 'A', disabled: false, stepInterval: 60, having: [], legend: '', limit: null, orderBy: [], groupBy: [ { key: SPAN_ATTRIBUTES.URL_PATH, dataType: DataTypes.String, type: 'tag', isColumn: false, isJSON: false, }, ], reduceTo: 'avg', }, ], queryFormulas: [], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, variables: {}, formatForWeb: true, start, end, step: 60, }, ]; interface EndPointMetricsResponseRow { data: { A: number | string; B: number | string; C: number | string; D: number | string; F1: number | string; }; } interface EndPointStatusCodeResponseRow { data: { response_status_code: string; A: number | string; B: number | string; C: number | string; }; } interface EndPointMetricsData { key: string; rate: number | string; latency: number | string; errorRate: number | string; lastUsed: string; } interface EndPointStatusCodeData { key: string; statusCode: string; count: number | string; rate: number | string; p99Latency: number | string; } export const getFormattedEndPointMetricsData = ( data: EndPointMetricsResponseRow[], ): EndPointMetricsData => { if (!data || data.length === 0) { return { key: v4(), rate: '-', latency: '-', errorRate: 0, lastUsed: '-', }; } return { key: v4(), rate: data[0].data.A === 'n/a' || !data[0].data.A ? '-' : data[0].data.A, latency: data[0].data.B === 'n/a' || data[0].data.B === undefined ? '-' : Math.round(Number(data[0].data.B) / 1000000), errorRate: data[0].data.F1 === 'n/a' || !data[0].data.F1 ? 0 : Number(data[0].data.F1), lastUsed: data[0].data.D === 'n/a' || !data[0].data.D ? '-' : getLastUsedRelativeTime(Math.floor(Number(data[0].data.D) / 1000000)), }; }; export const getFormattedEndPointStatusCodeData = ( data: EndPointStatusCodeResponseRow[], ): EndPointStatusCodeData[] => { if (!data) return []; return data.map((row) => ({ key: v4(), statusCode: row.data.response_status_code === 'n/a' || row.data.response_status_code === undefined ? '-' : row.data.response_status_code, count: row.data.A === 'n/a' || row.data.A === undefined ? '-' : row.data.A, rate: row.data.C === 'n/a' || row.data.C === undefined ? '-' : row.data.C, p99Latency: row.data.B === 'n/a' || row.data.B === undefined ? '-' : Math.round(Number(row.data.B) / 1000000), // Convert from nanoseconds to milliseconds, })); }; export const endPointStatusCodeColumns: ColumnType[] = [ { title:
STATUS CODE
, dataIndex: 'statusCode', key: 'statusCode', render: (text): JSX.Element => (
{text}
), sorter: (a: EndPointStatusCodeData, b: EndPointStatusCodeData): number => { const statusCodeA = a.statusCode === '-' || a.statusCode === 'n/a' ? 0 : Number(a.statusCode); const statusCodeB = b.statusCode === '-' || b.statusCode === 'n/a' ? 0 : Number(b.statusCode); return statusCodeA - statusCodeB; }, }, { title: (
NUMBER OF CALLS
), dataIndex: 'count', key: 'count', align: 'right', sorter: (a: EndPointStatusCodeData, b: EndPointStatusCodeData): number => { const countA = a.count === '-' || a.count === 'n/a' ? 0 : Number(a.count); const countB = b.count === '-' || b.count === 'n/a' ? 0 : Number(b.count); return countA - countB; }, }, { title: 'RATE', dataIndex: 'rate', key: 'rate', align: 'right', render: (rate: number | string): ReactNode => ( {rate !== '-' && rate !== 'n/a' ? `${rate}ops/sec` : '-'} ), sorter: (a: EndPointStatusCodeData, b: EndPointStatusCodeData): number => { const rateA = a.rate === '-' || a.rate === 'n/a' ? 0 : Number(a.rate); const rateB = b.rate === '-' || b.rate === 'n/a' ? 0 : Number(b.rate); return rateA - rateB; }, }, { title: 'P99 Latency', dataIndex: 'p99Latency', key: 'p99Latency', align: 'right', sorter: (a: EndPointStatusCodeData, b: EndPointStatusCodeData): number => { const p99LatencyA = a.p99Latency === '-' || a.p99Latency === 'n/a' ? 0 : Number(a.p99Latency); const p99LatencyB = b.p99Latency === '-' || b.p99Latency === 'n/a' ? 0 : Number(b.p99Latency); return p99LatencyA - p99LatencyB; }, render: (latency: number): ReactNode => {latency || '-'}ms, }, ]; export const statusCodeWidgetInfo = [ { yAxisUnit: 'calls' }, { yAxisUnit: 'ns' }, ]; interface EndPointDropDownResponseRow { data: { [SPAN_ATTRIBUTES.URL_PATH]: string; A: number; }; } interface EndPointDropDownData { key: string; label: string; value: string; } export const getFormattedEndPointDropDownData = ( data: EndPointDropDownResponseRow[], ): EndPointDropDownData[] => { if (!data) return []; return data.map((row) => ({ key: v4(), label: row.data[SPAN_ATTRIBUTES.URL_PATH] || '-', value: row.data[SPAN_ATTRIBUTES.URL_PATH] || '-', })); }; interface DependentServicesResponseRow { data: { ['service.name']: string; A: number | string; B: number | string; C: number | string; F1: number | string; }; } export interface ServiceData { serviceName: string; count: number | string; percentage: number | string; } export interface DependentServicesData { key: string; serviceData: ServiceData; latency: number | string; rate: number | string; errorPercentage: number | string; } // Discuss once about type safety of this function export const getFormattedDependentServicesData = ( data: DependentServicesResponseRow[], // eslint-disable-next-line sonarjs/cognitive-complexity ): DependentServicesData[] => { if (!data) return []; const totalCount = data?.reduce((acc, row) => acc + Number(row.data.A), 0); return data?.map((row) => ({ key: v4(), serviceData: { serviceName: row.data['service.name'] || '-', count: row.data.A !== undefined && row.data.A !== 'n/a' ? Number(row.data.A) : '-', percentage: totalCount > 0 && row.data.A !== undefined && row.data.A !== 'n/a' ? Number(((Number(row.data.A) / totalCount) * 100).toFixed(2)) : 0, }, latency: row.data.B !== undefined && row.data.B !== 'n/a' ? Math.round(Number(row.data.B) / 1000000) : '-', rate: row.data.C !== undefined && row.data.C !== 'n/a' ? row.data.C : '-', errorPercentage: row.data.F1 !== undefined && row.data.F1 !== 'n/a' ? row.data.F1 : 0, })); }; export const dependentServicesColumns: ColumnType[] = [ { title: Dependent Services, dataIndex: 'serviceData', key: 'serviceData', render: (serviceData: ServiceData): ReactNode => (
{serviceData.serviceName}
{serviceData.count} Calls
{typeof serviceData.percentage === 'number' ? serviceData.percentage.toFixed(2) : serviceData.percentage} %
), sorter: (a: DependentServicesData, b: DependentServicesData): number => { const countA = a.serviceData.count === '-' || a.serviceData.count === 'n/a' ? 0 : Number(a.serviceData.count); const countB = b.serviceData.count === '-' || b.serviceData.count === 'n/a' ? 0 : Number(b.serviceData.count); return countA - countB; }, }, { title: ( AVG. LATENCY ), dataIndex: 'latency', key: 'latency', render: (latency: number): ReactNode => (
{latency || '-'}ms
), sorter: (a: DependentServicesData, b: DependentServicesData): number => { const latencyA = a.latency === '-' || a.latency === 'n/a' ? 0 : Number(a.latency); const latencyB = b.latency === '-' || b.latency === 'n/a' ? 0 : Number(b.latency); return latencyA - latencyB; }, }, { title: ( ERROR % ), dataIndex: 'errorPercentage', key: 'errorPercentage', align: 'center', render: ( errorPercentage: number | string, // eslint-disable-next-line sonarjs/no-identical-functions ): React.ReactNode => { const errorPercentageValue = errorPercentage === 'n/a' || errorPercentage === '-' ? 0 : errorPercentage; return ( { const errorPercentagePercent = Number( (errorPercentageValue as number).toFixed(2), ); if (errorPercentagePercent >= 90) return Color.BG_SAKURA_500; if (errorPercentagePercent >= 60) return Color.BG_AMBER_500; return Color.BG_FOREST_500; })()} className="progress-bar error-rate" /> ); }, sorter: (a: DependentServicesData, b: DependentServicesData): number => { const errorPercentageA = a.errorPercentage === '-' || a.errorPercentage === 'n/a' ? 0 : Number(a.errorPercentage); const errorPercentageB = b.errorPercentage === '-' || b.errorPercentage === 'n/a' ? 0 : Number(b.errorPercentage); return errorPercentageA - errorPercentageB; }, }, { title: ( AVG. RATE ), dataIndex: 'rate', key: 'rate', align: 'right', render: (rate: number): ReactNode => (
{rate || '-'} ops/sec
), // eslint-disable-next-line sonarjs/no-identical-functions sorter: (a: DependentServicesData, b: DependentServicesData): number => { const rateA = a.rate === '-' || a.rate === 'n/a' ? 0 : Number(a.rate); const rateB = b.rate === '-' || b.rate === 'n/a' ? 0 : Number(b.rate); return rateA - rateB; }, }, ]; export const getFormattedChartData = ( data: MetricRangePayloadProps, newLegendArray: string[], ): MetricRangePayloadProps => { const result = cloneDeep(data); if (result?.data?.result) { result.data.result = result?.data?.result?.map((series, index) => ({ ...series, legend: newLegendArray[index], })); } return result; }; const getStatusCodeClass = (statusCode: string): string => { const code = parseInt(statusCode, 10); if (Number.isNaN(code)) { return 'Other'; } if (code >= 200 && code < 300) { return '200-299'; } if (code >= 300 && code < 400) { return '300-399'; } if (code >= 400 && code < 500) { return '400-499'; } if (code >= 500 && code < 600) { return '500-599'; } return 'Other'; }; export const groupStatusCodes = ( seriesList: QueryData[], aggregationType: 'sum' | 'average' = 'sum', // eslint-disable-next-line sonarjs/cognitive-complexity ): QueryData[] => { if (!seriesList?.length) { return seriesList; } const result = cloneDeep(seriesList); // Group series by status code class const groupedSeries: Record = {}; // Initialize timestamp map to track all timestamps across all series const allTimestamps = new Set(); // First pass: collect all series and timestamps result.forEach((series) => { const statusCode = series.metric?.response_status_code; if (!statusCode) return; const statusClass = getStatusCodeClass(statusCode); // Track all timestamps series.values.forEach((value) => { allTimestamps.add(Number(value[0])); }); // Initialize or update the grouped series if (!groupedSeries[statusClass]) { groupedSeries[statusClass] = { metric: { response_status_code: statusClass, }, values: [], queryName: series.queryName, legend: series.legend || statusClass, }; } }); // Create a sorted array of all timestamps const sortedTimestamps = Array.from(allTimestamps).sort((a, b) => a - b); // Initialize values and counters for each timestamp with zeros for each group const timestampValues: Record> = {}; const timestampCounts: Record> = {}; Object.keys(groupedSeries).forEach((group) => { timestampValues[group] = {}; timestampCounts[group] = {}; sortedTimestamps.forEach((timestamp) => { timestampValues[group][timestamp] = 0; timestampCounts[group][timestamp] = 0; }); }); // Second pass: aggregate values by status class and timestamp result.forEach((series) => { const statusCode = series.metric?.response_status_code; if (!statusCode) return; const statusClass = getStatusCodeClass(statusCode); series.values.forEach((value) => { const timestamp = value[0]; const numValue = parseFloat(value[1]); if (!Number.isNaN(numValue)) { timestampValues[statusClass][timestamp] += numValue; timestampCounts[statusClass][timestamp] += 1; } }); }); // Convert aggregated values back to series format Object.keys(groupedSeries).forEach((group) => { groupedSeries[group].values = sortedTimestamps.map((timestamp) => { let finalValue: number; if (aggregationType === 'average' && timestampCounts[group][timestamp] > 0) { // Calculate average if aggregationType is average finalValue = timestampValues[group][timestamp] / timestampCounts[group][timestamp]; } else { // Otherwise, use the sum finalValue = timestampValues[group][timestamp]; } return [timestamp, finalValue.toString()]; }); }); // Define the order of status code ranges const statusCodeOrder = ['200-299', '300-399', '400-499', '500-599', 'Other']; // Return the grouped series in the specified order return statusCodeOrder .filter((code) => groupedSeries[code]) // Only include codes that exist in the data .map((code) => groupedSeries[code]); }; export const getStatusCodeBarChartWidgetData = ( domainName: string, endPointName: string, filters: IBuilderQuery['filters'], ): Widgets => ({ query: { builder: { queryData: [ { aggregateAttribute: { dataType: DataTypes.String, id: '------false', isColumn: false, key: '', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: 'c6724407', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, ...(endPointName ? [ { id: '8b1be6f0', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.URL_PATH, type: 'tag', }, op: '=', value: endPointName, }, ] : []), ...filters.items, ], op: 'AND', }, functions: [], groupBy: [], having: [], legend: '', limit: null, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'rate', }, ], queryFormulas: [], }, clickhouse_sql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', promql: [ { disabled: false, legend: '', name: 'A', query: '', }, ], queryType: EQueryType.QUERY_BUILDER, }, description: '', id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2', isStacked: false, panelTypes: PANEL_TYPES.BAR, title: '', opacity: '', nullZeroValues: '', timePreferance: 'GLOBAL_TIME', softMin: null, softMax: null, selectedLogFields: null, selectedTracesFields: null, }); interface EndPointStatusCodePayloadData { data: { result: QueryData[]; newResult: any; resultType: string; }; } export const getFormattedEndPointStatusCodeChartData = ( data: EndPointStatusCodePayloadData, aggregationType: 'sum' | 'average' = 'sum', ): EndPointStatusCodePayloadData => { if (!data) { return { data: { result: [], newResult: [], resultType: 'matrix', }, }; } return { data: { result: groupStatusCodes(data?.data?.result, aggregationType), newResult: data?.data?.newResult, resultType: data?.data?.resultType, }, }; }; export const END_POINT_DETAILS_QUERY_KEYS_ARRAY = [ REACT_QUERY_KEY.GET_ENDPOINT_METRICS_DATA, REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_DATA, REACT_QUERY_KEY.GET_ENDPOINT_RATE_OVER_TIME_DATA, REACT_QUERY_KEY.GET_ENDPOINT_LATENCY_OVER_TIME_DATA, REACT_QUERY_KEY.GET_ENDPOINT_DROPDOWN_DATA, REACT_QUERY_KEY.GET_ENDPOINT_DEPENDENT_SERVICES_DATA, REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_BAR_CHARTS_DATA, REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA, ]; export const getAllEndpointsWidgetData = ( groupBy: BaseAutocompleteData[], domainName: string, filters: IBuilderQuery['filters'], // eslint-disable-next-line sonarjs/cognitive-complexity ): Widgets => { const isGroupedByAttribute = groupBy.length > 0; const widget = getWidgetQueryBuilder( getWidgetQuery({ title: 'Endpoint Overview', description: 'Endpoint Overview', panelTypes: PANEL_TYPES.TABLE, queryData: [ { aggregateAttribute: { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'span_id', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: 'ec316e57', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: isGroupedByAttribute ? [...defaultGroupBy, ...groupBy] : defaultGroupBy, having: [], legend: 'Num of Calls', limit: 1000, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'count', }, { aggregateAttribute: { dataType: DataTypes.Float64, isColumn: true, isJSON: false, key: 'duration_nano', type: '', }, aggregateOperator: 'p99', dataSource: DataSource.TRACES, disabled: false, expression: 'B', filters: { items: [ { id: '46d57857', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: isGroupedByAttribute ? [...defaultGroupBy, ...groupBy] : defaultGroupBy, having: [], legend: 'Latency (ms)', limit: 1000, orderBy: [], queryName: 'B', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'p99', }, { aggregateAttribute: { dataType: DataTypes.String, id: 'timestamp------false', isColumn: false, key: 'timestamp', type: '', }, aggregateOperator: 'max', dataSource: DataSource.TRACES, disabled: false, expression: 'C', filters: { items: [ { id: '4a237616', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: isGroupedByAttribute ? [...defaultGroupBy, ...groupBy] : defaultGroupBy, having: [], legend: 'Last Used', limit: 1000, orderBy: [], queryName: 'C', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'max', }, { aggregateAttribute: { dataType: DataTypes.String, isColumn: true, isJSON: false, key: 'span_id', type: '', }, aggregateOperator: 'count', dataSource: DataSource.TRACES, disabled: true, expression: 'D', filters: { items: [ { id: 'f162de1e', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, { id: '3df0ac1d', key: { dataType: DataTypes.bool, isColumn: true, isJSON: false, key: 'has_error', type: '', }, op: '=', value: 'true', }, { id: '212678b9', key: { key: 'kind_string', dataType: DataTypes.String, type: '', isColumn: true, isJSON: false, }, op: '=', value: 'Client', }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: isGroupedByAttribute ? [...defaultGroupBy, ...groupBy] : defaultGroupBy, having: [], legend: '', limit: 1000, orderBy: [], queryName: 'D', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'count', }, ], queryFormulas: [ { queryName: 'F1', expression: '(D/A)*100', disabled: false, legend: 'error percentage', }, ], yAxisUnit: 'ops/s', }), ); widget.renderColumnCell = { [SPAN_ATTRIBUTES.URL_PATH]: (url: any): ReactNode => { const { endpoint } = extractPortAndEndpoint(url); return ( {endpoint === 'n/a' || url === undefined ? '-' : endpoint} ); }, A: (numOfCalls: any): ReactNode => ( {numOfCalls === 'n/a' || numOfCalls === undefined ? '-' : numOfCalls} ), B: (latency: any): ReactNode => ( {latency === 'n/a' || latency === undefined ? '-' : `${Math.round(Number(latency) / 1000000)} ms`} ), C: (lastUsed: any): ReactNode => ( {lastUsed === 'n/a' || lastUsed === undefined ? '-' : getLastUsedRelativeTime( new Date( new Date(Math.floor(Number(lastUsed) / 1000000)).toISOString(), ).getTime(), )} ), F1: (errorRate: any): ReactNode => ( { const errorRatePercent = Number( ((errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number).toFixed(2), ); if (errorRatePercent >= 90) return Color.BG_SAKURA_500; if (errorRatePercent >= 60) return Color.BG_AMBER_500; return Color.BG_FOREST_500; })()} className="progress-bar error-rate" /> ), }; widget.customColTitles = { [SPAN_ATTRIBUTES.URL_PATH]: 'Endpoint', 'net.peer.port': 'Port', }; widget.title = (
Endpoint Overview
); return widget; }; const keysToRemove = ['http.url', 'A', 'B', 'C', 'F1']; export const getGroupByFiltersFromGroupByValues = ( rowData: any, groupBy: BaseAutocompleteData[], ): IBuilderQuery['filters'] => { const items = Object.keys(rowData) .filter((key) => !keysToRemove.includes(key)) .map((key) => { const groupByAttribute = groupBy.find((gb) => gb.key === key); return { id: groupByAttribute?.id || v4(), key: { dataType: groupByAttribute?.dataType || DataTypes.String, isColumn: groupByAttribute?.isColumn || true, isJSON: groupByAttribute?.isJSON || false, key: groupByAttribute?.key || key, type: groupByAttribute?.type || '', }, op: '=', // operator for every attribute -> discuss value: rowData[key], }; }); return { items, op: 'AND', }; }; export const getRateOverTimeWidgetData = ( domainName: string, endPointName: string, filters: IBuilderQuery['filters'], ): Widgets => { let legend = domainName; if (endPointName) { const { endpoint, port } = extractPortAndEndpoint(endPointName); // eslint-disable-next-line sonarjs/no-nested-template-literals legend = `${port !== '-' && port !== 'n/a' ? `${port}:` : ''}${endpoint}`; } return getWidgetQueryBuilder( getWidgetQuery({ title: 'Rate Over Time', description: 'Rate over time.', queryData: [ { aggregateAttribute: { dataType: DataTypes.String, id: '------false', isColumn: false, key: '', type: '', }, aggregateOperator: 'rate', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: '3c76fe0b', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [], having: [], legend, limit: null, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'rate', }, ], yAxisUnit: 'ops/s', }), ); }; export const getLatencyOverTimeWidgetData = ( domainName: string, endPointName: string, filters: IBuilderQuery['filters'], ): Widgets => { let legend = domainName; if (endPointName) { const { endpoint, port } = extractPortAndEndpoint(endPointName); // eslint-disable-next-line sonarjs/no-nested-template-literals legend = `${port !== '-' && port !== 'n/a' ? `${port}:` : ''}${endpoint}`; } return getWidgetQueryBuilder( getWidgetQuery({ title: 'Latency Over Time', description: 'Latency over time.', queryData: [ { aggregateAttribute: { dataType: DataTypes.Float64, isColumn: true, isJSON: false, key: 'duration_nano', type: '', }, aggregateOperator: 'p99', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { items: [ { id: '63adb3ff', key: { dataType: DataTypes.String, isColumn: false, isJSON: false, key: SPAN_ATTRIBUTES.SERVER_NAME, type: 'tag', }, op: '=', value: domainName, }, ...filters.items, ], op: 'AND', }, functions: [], groupBy: [], having: [], legend, limit: null, orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, timeAggregation: 'p99', }, ], yAxisUnit: 'ns', }), ); }; /** * Helper function to get the start and end status codes from a status code range string * @param value Status code range string (e.g. '200-299') or boolean * @returns Tuple of [startStatusCode, endStatusCode] as strings */ const getStartAndEndStatusCode = ( value: string | boolean, ): [string, string] => { if (!value) { return ['', '']; } switch (value) { case '100-199': return ['100', '199']; case '200-299': return ['200', '299']; case '300-399': return ['300', '399']; case '400-499': return ['400', '499']; case '500-599': return ['500', '599']; default: return ['', '']; } }; /** * Creates filter items for bar chart based on group by fields and request data * Used specifically for filtering status code ranges in bar charts * @param groupBy Array of group by fields to create filters for * @param requestData Data from graph click containing values to filter on * @returns Array of TagFilterItems with >= and < operators for status code ranges */ export const createGroupByFiltersForBarChart = ( groupBy: BaseAutocompleteData[], requestData: GraphClickMetaData, ): TagFilterItem[] => groupBy .map((gb) => { const value = requestData[gb.key]; const [startStatusCode, endStatusCode] = getStartAndEndStatusCode(value); return value ? [ { id: v4(), key: gb, op: '>=', value: startStatusCode, }, { id: v4(), key: gb, op: '<=', value: endStatusCode, }, ] : []; }) .flat(); export const getCustomFiltersForBarChart = ( metric: | { [key: string]: string; } | undefined, ): TagFilterItem[] => { if (!metric?.response_status_code) { return []; } const [startStatusCode, endStatusCode] = getStartAndEndStatusCode( metric.response_status_code, ); return [ { id: v4(), key: { dataType: DataTypes.String, id: 'response_status_code--string--tag--false', isColumn: false, isJSON: false, key: 'response_status_code', type: 'tag', }, op: '>=', value: startStatusCode, }, { id: v4(), key: { dataType: DataTypes.String, id: 'response_status_code--string--tag--false', isColumn: false, isJSON: false, key: 'response_status_code', type: 'tag', }, op: '<=', value: endStatusCode, }, ]; };