diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts index 09d75871a3..a1671dbbb5 100644 --- a/frontend/src/constants/reactQueryKeys.ts +++ b/frontend/src/constants/reactQueryKeys.ts @@ -54,6 +54,7 @@ export const REACT_QUERY_KEY = { // API Monitoring Query Keys GET_DOMAINS_LIST: 'GET_DOMAINS_LIST', + GET_DOMAIN_METRICS_DATA: 'GET_DOMAIN_METRICS_DATA', GET_ENDPOINTS_LIST_BY_DOMAIN: 'GET_ENDPOINTS_LIST_BY_DOMAIN', GET_TOP_ERRORS_BY_DOMAIN: 'GET_TOP_ERRORS_BY_DOMAIN', GET_NESTED_ENDPOINTS_LIST: 'GET_NESTED_ENDPOINTS_LIST', diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx index 60028fa5fe..8f28a6984a 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx @@ -159,7 +159,7 @@ function AllEndPoints({ const handleRowClick = (record: EndPointsTableRowData): void => { if (groupBy.length === 0) { setSelectedEndPointName(record.endpointName); // this will open up the endpoint details tab - setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS); + setSelectedView(VIEW_TYPES.ENDPOINT_STATS); logEvent('API Monitoring: Endpoint name row clicked', {}); } else { handleGroupByRowClick(record); // this will prepare the nested query payload diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.styles.scss b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.styles.scss index a656032da7..dc7775414b 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.styles.scss +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.styles.scss @@ -252,6 +252,9 @@ border: 1px solid var(--bg-slate-500); .endpoints-table-header { + display: flex; + align-items: center; + gap: 10px; padding: 12px; color: var(--Vanilla-100, #fff); font-family: Inter; @@ -392,6 +395,21 @@ padding-top: 20px; } +.top-errors-dropdown-container { + display: flex; + flex-direction: row; + gap: 10px; + align-items: center; + + .endpoint-details-filters-container-dropdown { + width: 100%; + } + + .endpoint-details-filters-container-search { + flex: 1; + } +} + .endpoint-details-container { display: flex; flex-direction: column; diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx index 93100d92a7..0f4d44c724 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx @@ -20,7 +20,7 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import AllEndPoints from './AllEndPoints'; import DomainMetrics from './components/DomainMetrics'; import { VIEW_TYPES, VIEWS } from './constants'; -import EndPointDetailsWrapper from './EndPointDetailsWrapper'; +import EndPointDetails from './EndPointDetails'; import TopErrors from './TopErrors'; const TimeRangeOffset = 1000000000; @@ -156,7 +156,10 @@ function DomainDetails({ > {domainData && ( <> - +
-
Endpoint Details
+
Endpoint(s) Stats
-
Top Errors
+
Top 10 Errors
@@ -203,13 +206,14 @@ function DomainDetails({ /> )} - {selectedView === VIEW_TYPES.ENDPOINT_DETAILS && ( - )} diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetails.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetails.tsx index 540dc98b56..9f646355cf 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetails.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/EndPointDetails.tsx @@ -8,6 +8,10 @@ import { getRateOverTimeWidgetData, } from 'container/ApiMonitoring/utils'; import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2'; +// import { +// CustomTimeType, +// Time, +// } from 'container/TopNav/DateTimeSelectionV2/config'; import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; import { useMemo, useState } from 'react'; import { useQueries } from 'react-query'; @@ -29,7 +33,8 @@ function EndPointDetails({ setSelectedEndPointName, domainListFilters, timeRange, -}: { +}: // handleTimeChange, +{ domainName: string; endPointName: string; setSelectedEndPointName: (value: string) => void; @@ -38,6 +43,10 @@ function EndPointDetails({ startTime: number; endTime: number; }; + // handleTimeChange: ( + // interval: Time | CustomTimeType, + // dateTimeRange?: [number, number], + // ) => void; }): JSX.Element { const { startTime: minTime, endTime: maxTime } = timeRange; @@ -47,6 +56,7 @@ function EndPointDetails({ op: 'AND', items: [], }); + // [TODO] if endPointName is there then add it to the filters under http.url key // Manually update the query to include the filters // Because using the hook is causing the global domain @@ -78,15 +88,8 @@ function EndPointDetails({ ); const endPointDetailsQueryPayload = useMemo( - () => - getEndPointDetailsQueryPayload( - domainName, - endPointName, - minTime, - maxTime, - filters, - ), - [domainName, endPointName, filters, minTime, maxTime], + () => getEndPointDetailsQueryPayload(domainName, minTime, maxTime, filters), + [domainName, filters, minTime, maxTime], ); const endPointDetailsDataQueries = useQueries( @@ -141,6 +144,20 @@ function EndPointDetails({ [domainName, endPointName, filters, domainListFilters], ); + // // [TODO] Fix this later + // const onDragSelect = useCallback( + // (start: number, end: number) => { + // const startTimestamp = Math.trunc(start); + // const endTimestamp = Math.trunc(end); + + // if (startTimestamp !== endTimestamp) { + // // update the value in local time picker + // handleTimeChange('custom', [startTimestamp, endTimestamp]); + // } + // }, + // [handleTimeChange], + // ); + return (
@@ -166,7 +183,9 @@ function EndPointDetails({
Endpoint
-
{endpoint || '-'}
+
+ {endpoint || 'All Endpoints'} +
Port
@@ -177,6 +196,7 @@ function EndPointDetails({ {!isServicesFilterApplied && ( )} - - + {}} + /> + {}} + />
); } diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx index d04effedb3..0022174dbe 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx @@ -1,19 +1,24 @@ import { LoadingOutlined } from '@ant-design/icons'; -import { Spin, Table, Typography } from 'antd'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { Spin, Table, Tooltip, Typography } from 'antd'; +import { DEFAULT_ENTITY_VERSION, ENTITY_VERSION_V4 } from 'constants/app'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { + END_POINT_DETAILS_QUERY_KEYS_ARRAY, formatTopErrorsDataForTable, + getEndPointDetailsQueryPayload, getTopErrorsColumnsConfig, getTopErrorsQueryPayload, TopErrorsResponseRow, } from 'container/ApiMonitoring/utils'; import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { useMemo } from 'react'; +import { Info } from 'lucide-react'; +import { useMemo, useState } from 'react'; import { useQueries } from 'react-query'; import { SuccessResponse } from 'types/api'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import EndPointsDropDown from './components/EndPointsDropDown'; import ErrorState from './components/ErrorState'; function TopErrors({ @@ -28,9 +33,31 @@ function TopErrors({ }): JSX.Element { const { startTime: minTime, endTime: maxTime } = timeRange; + const [endPointName, setSelectedEndPointName] = useState(''); + const queryPayloads = useMemo( - () => getTopErrorsQueryPayload(domainName, minTime, maxTime), - [domainName, minTime, maxTime], + () => + getTopErrorsQueryPayload(domainName, minTime, maxTime, { + items: endPointName + ? [ + { + id: '92b8a1c1', + key: { + dataType: DataTypes.String, + id: 'http.url--string--tag--false', + isColumn: false, + isJSON: false, + key: 'http.url', + type: 'tag', + }, + op: '=', + value: endPointName, + }, + ] + : [], + op: 'AND', + }), + [domainName, endPointName, minTime, maxTime], ); // Since only one query here @@ -67,6 +94,35 @@ function TopErrors({ [topErrorsData], ); + const endPointDropDownQueryPayload = useMemo( + () => [ + getEndPointDetailsQueryPayload(domainName, minTime, maxTime, { + items: [], + op: 'AND', + })[2], + ], + [domainName, minTime, maxTime], + ); + + const endPointDropDownDataQueries = useQueries( + endPointDropDownQueryPayload.map((payload) => ({ + queryKey: [ + END_POINT_DETAILS_QUERY_KEYS_ARRAY[4], + payload, + ENTITY_VERSION_V4, + ], + queryFn: (): Promise> => + GetMetricQueryRange(payload, ENTITY_VERSION_V4), + enabled: !!payload, + staleTime: 60 * 1000, // 1 minute stale time : optimize this part + })), + ); + + const [endPointDropDownDataQuery] = useMemo( + () => [endPointDropDownDataQueries[0]], + [endPointDropDownDataQueries], + ); + if (isError) { return (
@@ -77,8 +133,27 @@ function TopErrors({ return (
+
+
+ +
+ + + +
+
-
Top Errors
+
+ Top Errors{' '} + + + +
, unknown>; + timeRange: { + startTime: number; + endTime: number; + }; } function DependentServices({ dependentServicesQuery, + timeRange, }: DependentServicesProps): JSX.Element { const { data, @@ -85,6 +91,25 @@ function DependentServices({ ), }} + onRow={(record): { onClick: () => void; className: string } => ({ + onClick: (): void => { + const url = new URL( + `/services/${ + record.serviceData.serviceName && + record.serviceData.serviceName !== '-' + ? record.serviceData.serviceName + : '' + }`, + window.location.origin, + ); + const urlQuery = new URLSearchParams(); + urlQuery.set(QueryParams.startTime, timeRange.startTime.toString()); + urlQuery.set(QueryParams.endTime, timeRange.endTime.toString()); + url.search = urlQuery.toString(); + window.open(url.toString(), '_blank'); + }, + className: 'clickable-row', + })} /> {dependentServicesData.length > 5 && ( diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/DomainMetrics.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/DomainMetrics.tsx index ec87a78894..2015df228b 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/DomainMetrics.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/DomainMetrics.tsx @@ -1,8 +1,79 @@ import { Color } from '@signozhq/design-tokens'; -import { Progress, Tooltip, Typography } from 'antd'; -import { getLastUsedRelativeTime } from 'container/ApiMonitoring/utils'; +import { Progress, Skeleton, Tooltip, Typography } from 'antd'; +import { ENTITY_VERSION_V4 } from 'constants/app'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { + DomainMetricsResponseRow, + formatDomainMetricsDataForTable, + getDomainMetricsQueryPayload, +} from 'container/ApiMonitoring/utils'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { useMemo } from 'react'; +import { useQueries } from 'react-query'; +import { SuccessResponse } from 'types/api'; +import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; + +import ErrorState from './ErrorState'; + +function DomainMetrics({ + domainName, + timeRange, +}: { + domainName: string; + timeRange: { startTime: number; endTime: number }; +}): JSX.Element { + const { startTime: minTime, endTime: maxTime } = timeRange; + + const queryPayloads = useMemo( + () => getDomainMetricsQueryPayload(domainName, minTime, maxTime), + [domainName, minTime, maxTime], + ); + + // Since only one query here + const domainMetricsDataQueries = useQueries( + queryPayloads.map((payload) => ({ + queryKey: [ + REACT_QUERY_KEY.GET_DOMAIN_METRICS_DATA, + payload, + ENTITY_VERSION_V4, + ], + queryFn: (): Promise> => + GetMetricQueryRange(payload, ENTITY_VERSION_V4), + enabled: !!payload, + staleTime: 60 * 1000, // 1 minute stale time : optimize this part + })), + ); + + const domainMetricsDataQuery = domainMetricsDataQueries[0]; + // [TODO] handle the case where the data is not available + // [TODO] Format the data properly + const { + data: domainMetricsData, + isLoading, + isRefetching, + isError, + refetch, + } = domainMetricsDataQuery; + + // [TODO] Fix type error + const formattedDomainMetricsData = useMemo(() => { + // Safely access the data with proper type checking + const rowData = domainMetricsData?.payload?.data?.result[0]?.table?.rows[0]; + + // Only pass the data if it matches the expected format + return formatDomainMetricsDataForTable( + rowData as DomainMetricsResponseRow | undefined, + ); + }, [domainMetricsData]); + + if (isError) { + return ( +
+ +
+ ); + } -function DomainMetrics({ domainData }: { domainData: any }): JSX.Element { return (
@@ -35,41 +106,62 @@ function DomainMetrics({ domainData }: { domainData: any }): JSX.Element {
- - {domainData.endpointCount} - + {isLoading || isRefetching ? ( + + ) : ( + + + {formattedDomainMetricsData.endpointCount} + + + )} {/* // update the tooltip as well */} - - - {(domainData.latency / 1000).toFixed(3)}s - - + {isLoading || isRefetching ? ( + + ) : ( + + + {(Number(formattedDomainMetricsData.latency) / 1000).toFixed(3)}s + + + )} {/* // update the tooltip as well */} - - { - const errorRatePercent = Number(domainData.errorRate.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" - /> - + {isLoading || isRefetching ? ( + + ) : ( + + { + const errorRatePercent = Number( + Number(formattedDomainMetricsData.errorRate).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" + /> + + )} - {/* // update the tooltip as well */} - - {getLastUsedRelativeTime(domainData.lastUsed)} - + {isLoading || isRefetching ? ( + + ) : ( + + {formattedDomainMetricsData.lastUsed} + + )}
diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointsDropDown.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointsDropDown.tsx index a351cf4208..f3583a561d 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointsDropDown.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/EndPointsDropDown.tsx @@ -52,6 +52,10 @@ function EndPointsDropDown({ : (triggerNode): HTMLElement => triggerNode.parentNode as HTMLElement } dropdownStyle={dropdownStyle} + allowClear + onClear={(): void => { + setSelectedEndPointName(''); + }} /> ); } diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/ExpandedRow.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/ExpandedRow.tsx index 26cb03c09f..c28417688b 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/ExpandedRow.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/ExpandedRow.tsx @@ -118,7 +118,7 @@ function ExpandedRow({ onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => { setSelectedEndPointName(record.endpointName); - setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS); + setSelectedView(VIEW_TYPES.ENDPOINT_STATS); logEvent('API Monitoring: Endpoint name row clicked', {}); }, className: 'expanded-clickable-row', diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/MetricOverTimeGraph.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/MetricOverTimeGraph.tsx index dadf1513c9..65de1e8b7d 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/MetricOverTimeGraph.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/components/MetricOverTimeGraph.tsx @@ -5,9 +5,11 @@ import { Widgets } from 'types/api/dashboard/getAll'; function MetricOverTimeGraph({ widget, timeRange, + onDragSelect, }: { widget: Widgets; timeRange: { startTime: number; endTime: number }; + onDragSelect: (start: number, end: number) => void; }): JSX.Element { return (
@@ -16,10 +18,9 @@ function MetricOverTimeGraph({ {}} + onDragSelect={onDragSelect} customOnDragSelect={(): void => {}} - start={timeRange.startTime} - end={timeRange.endTime} + customTimeRange={timeRange} />
diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/constants.ts b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/constants.ts index 1fbaf85bdc..aacb1c18a1 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/constants.ts +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/constants.ts @@ -1,11 +1,11 @@ export enum VIEWS { ALL_ENDPOINTS = 'all_endpoints', - ENDPOINT_DETAILS = 'endpoint_details', + ENDPOINT_STATS = 'endpoint_stats', TOP_ERRORS = 'top_errors', } export const VIEW_TYPES = { ALL_ENDPOINTS: VIEWS.ALL_ENDPOINTS, - ENDPOINT_DETAILS: VIEWS.ENDPOINT_DETAILS, + ENDPOINT_STATS: VIEWS.ENDPOINT_STATS, TOP_ERRORS: VIEWS.TOP_ERRORS, }; diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx index 192a98f6ed..a37c009282 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainList.tsx @@ -60,7 +60,6 @@ function DomainList({ showIP }: { showIP: boolean }): JSX.Element { aggregateAttribute: { ...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute, }, - queryName: '', }, ], }, diff --git a/frontend/src/container/ApiMonitoring/utils.tsx b/frontend/src/container/ApiMonitoring/utils.tsx index c9cb2035e3..96b6b1965b 100644 --- a/frontend/src/container/ApiMonitoring/utils.tsx +++ b/frontend/src/container/ApiMonitoring/utils.tsx @@ -333,6 +333,278 @@ export const formatDataForTable = ( ).toISOString(), // Convert from nanoseconds to milliseconds })); +export const getDomainMetricsQueryPayload = ( + 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, + id: 'http.url--string--tag--false', + isColumn: false, + isJSON: false, + key: 'http.url', + type: 'tag', + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters: { + items: [ + { + id: '4c57937c', + key: { + dataType: DataTypes.String, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + ], + 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, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + timeAggregation: 'p99', + spaceAggregation: 'sum', + functions: [], + filters: { + items: [ + { + id: '2cf675cd', + key: { + dataType: DataTypes.String, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + ], + 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, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + { + id: '6096f745', + key: { + dataType: DataTypes.bool, + id: 'has_error--bool----true', + isColumn: true, + isJSON: false, + key: 'has_error', + type: '', + }, + op: '=', + value: 'true', + }, + ], + 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, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + ], + 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 = [ { @@ -637,6 +909,7 @@ export const getTopErrorsQueryPayload = ( domainName: string, start: number, end: number, + filters: IBuilderQuery['filters'], ): GetQueryResultsProps[] => [ { selectedTime: 'GLOBAL_TIME', @@ -714,6 +987,7 @@ export const getTopErrorsQueryPayload = ( op: '=', value: domainName, }, + ...filters.items, ], }, expression: 'A', @@ -1192,7 +1466,6 @@ export const createFiltersForSelectedRowData = ( // Sixth query payload for endpoint response status code latency bar chart export const getEndPointDetailsQueryPayload = ( domainName: string, - endPointName: string, start: number, end: number, filters: IBuilderQuery['filters'], @@ -1218,19 +1491,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'A', filters: { items: [ - { - id: '92b8a1c1', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: '874562e1', key: { @@ -1288,19 +1548,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'B', filters: { items: [ - { - id: 'c0c0f76b', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: '0c5564e0', key: { @@ -1358,19 +1605,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'C', filters: { items: [ - { - id: '7a3eebed', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: '0d656701', key: { @@ -1440,19 +1674,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'D', filters: { items: [ - { - id: 'e7f12d52', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: '918f5b99', key: { @@ -1510,19 +1731,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'E', filters: { items: [ - { - id: '5281578a', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: 'b355d1aa', key: { @@ -1634,19 +1842,6 @@ export const getEndPointDetailsQueryPayload = ( op: '=', value: domainName, }, - { - id: 'e1b24204', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: '212678b9', key: { @@ -1713,19 +1908,6 @@ export const getEndPointDetailsQueryPayload = ( op: '=', value: domainName, }, - { - id: '5dbe3518', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: '212678b9', key: { @@ -1909,19 +2091,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'A', filters: { items: [ - { - id: 'bdac4904', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: 'b78ff216', key: { @@ -1988,19 +2157,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'B', filters: { items: [ - { - id: '74f9d185', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: 'a9024472', key: { @@ -2066,19 +2222,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'C', filters: { items: [ - { - id: 'b7e36a72', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: '1b6c062d', key: { @@ -2145,19 +2288,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'D', filters: { items: [ - { - id: 'ede7cbfe', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: 'd14792a8', key: { @@ -2290,19 +2420,6 @@ export const getEndPointDetailsQueryPayload = ( op: '=', value: domainName, }, - { - id: '8b1be6f0', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: '212678b9', key: { @@ -2390,19 +2507,6 @@ export const getEndPointDetailsQueryPayload = ( expression: 'A', filters: { items: [ - { - id: '52aca159', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, { id: 'aae93366', key: { @@ -2787,7 +2891,7 @@ export const dependentServicesColumns: ColumnType[] = [
{serviceData.serviceName}
-
{serviceData.count}
+
{serviceData.count} Calls
{ - const { endpoint, port } = extractPortAndEndpoint(endPointName); - const legend = `${ - port !== '-' && port !== 'n/a' ? `${port}:` : '' - }${endpoint}`; + 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', @@ -3213,34 +3320,12 @@ export const getRateOverTimeWidgetData = ( op: '=', value: domainName, }, - { - id: '30710f04', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, ...filters.items, ], op: 'AND', }, functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - ], + groupBy: [], having: [], legend, limit: null, @@ -3262,8 +3347,13 @@ export const getLatencyOverTimeWidgetData = ( endPointName: string, filters: IBuilderQuery['filters'], ): Widgets => { - const { endpoint, port } = extractPortAndEndpoint(endPointName); - const legend = `${port}:${endpoint}`; + 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', @@ -3297,34 +3387,12 @@ export const getLatencyOverTimeWidgetData = ( op: '=', value: domainName, }, - { - id: '50142500', - key: { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - op: '=', - value: endPointName, - }, ...filters.items, ], op: 'AND', }, functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'http.url--string--tag--false', - isColumn: false, - isJSON: false, - key: 'http.url', - type: 'tag', - }, - ], + groupBy: [], having: [], legend, limit: null, diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index add29fa1b0..667f5a9594 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -47,6 +47,7 @@ function GridCardGraph({ start, end, analyticsEvent, + customTimeRange, }: GridCardGraphProps): JSX.Element { const dispatch = useDispatch(); const [errorMessage, setErrorMessage] = useState(); @@ -130,6 +131,8 @@ function GridCardGraph({ variables: getDashboardVariables(variables), fillGaps: widget.fillSpans, formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE, + start: customTimeRange?.startTime || start, + end: customTimeRange?.endTime || end, }; } updatedQuery.builder.queryData[0].pageSize = 10; @@ -149,6 +152,8 @@ function GridCardGraph({ initialDataSource === DataSource.TRACES && widget.selectedTracesFields, }, fillGaps: widget.fillSpans, + start: customTimeRange?.startTime || start, + end: customTimeRange?.endTime || end, }; }); @@ -187,8 +192,8 @@ function GridCardGraph({ variables: getDashboardVariables(variables), selectedTime: widget.timePreferance || 'GLOBAL_TIME', globalSelectedInterval, - start, - end, + start: customTimeRange?.startTime || start, + end: customTimeRange?.endTime || end, }, version || DEFAULT_ENTITY_VERSION, { @@ -202,6 +207,9 @@ function GridCardGraph({ widget.timePreferance, widget.fillSpans, requestData, + ...(customTimeRange && customTimeRange.startTime && customTimeRange.endTime + ? [customTimeRange.startTime, customTimeRange.endTime] + : []), ], retry(failureCount, error): boolean { if ( diff --git a/frontend/src/container/GridCardLayout/GridCard/types.ts b/frontend/src/container/GridCardLayout/GridCard/types.ts index c943da6e7a..e1ba7cb918 100644 --- a/frontend/src/container/GridCardLayout/GridCard/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/types.ts @@ -61,6 +61,10 @@ export interface GridCardGraphProps { start?: number; end?: number; analyticsEvent?: string; + customTimeRange?: { + startTime: number; + endTime: number; + }; } export interface GetGraphVisibilityStateOnLegendClickProps {