feat: new dep services top 10 errors localised date picker agrregate domain details etc

This commit is contained in:
sawhil 2025-04-25 16:27:06 +05:30 committed by Sahil Khan
parent 8b30e3cc5c
commit 1123a9a93d
16 changed files with 617 additions and 290 deletions

View File

@ -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',

View File

@ -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

View File

@ -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;

View File

@ -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 && (
<>
<DomainMetrics domainData={domainData} />
<DomainMetrics
domainName={domainData.domainName}
timeRange={modalTimeRange}
/>
<div className="views-tabs-container">
<Radio.Group
className="views-tabs"
@ -174,13 +177,13 @@ function DomainDetails({
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.ENDPOINT_DETAILS
selectedView === VIEW_TYPES.ENDPOINT_STATS
? 'tab selected_view'
: 'tab'
}
value={VIEW_TYPES.ENDPOINT_DETAILS}
value={VIEW_TYPES.ENDPOINT_STATS}
>
<div className="view-title">Endpoint Details</div>
<div className="view-title">Endpoint(s) Stats</div>
</Radio.Button>
<Radio.Button
className={
@ -188,7 +191,7 @@ function DomainDetails({
}
value={VIEW_TYPES.TOP_ERRORS}
>
<div className="view-title">Top Errors</div>
<div className="view-title">Top 10 Errors</div>
</Radio.Button>
</Radio.Group>
</div>
@ -203,13 +206,14 @@ function DomainDetails({
/>
)}
{selectedView === VIEW_TYPES.ENDPOINT_DETAILS && (
<EndPointDetailsWrapper
{selectedView === VIEW_TYPES.ENDPOINT_STATS && (
<EndPointDetails
domainName={domainData.domainName}
endPointName={selectedEndPointName}
setSelectedEndPointName={setSelectedEndPointName}
domainListFilters={domainListFilters}
timeRange={modalTimeRange}
// handleTimeChange={handleTimeChange}
/>
)}

View File

@ -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 (
<div className="endpoint-details-container">
<div className="endpoint-details-filters-container">
@ -166,7 +183,9 @@ function EndPointDetails({
<div className="endpoint-meta-data">
<div className="endpoint-meta-data-pill">
<div className="endpoint-meta-data-label">Endpoint</div>
<div className="endpoint-meta-data-value">{endpoint || '-'}</div>
<div className="endpoint-meta-data-value">
{endpoint || 'All Endpoints'}
</div>
</div>
<div className="endpoint-meta-data-pill">
<div className="endpoint-meta-data-label">Port</div>
@ -177,6 +196,7 @@ function EndPointDetails({
{!isServicesFilterApplied && (
<DependentServices
dependentServicesQuery={endPointDependentServicesDataQuery}
timeRange={timeRange}
/>
)}
<StatusCodeBarCharts
@ -191,8 +211,16 @@ function EndPointDetails({
timeRange={timeRange}
/>
<StatusCodeTable endPointStatusCodeDataQuery={endPointStatusCodeDataQuery} />
<MetricOverTimeGraph widget={rateOverTimeWidget} timeRange={timeRange} />
<MetricOverTimeGraph widget={latencyOverTimeWidget} timeRange={timeRange} />
<MetricOverTimeGraph
widget={rateOverTimeWidget}
timeRange={timeRange}
onDragSelect={(): void => {}}
/>
<MetricOverTimeGraph
widget={latencyOverTimeWidget}
timeRange={timeRange}
onDragSelect={(): void => {}}
/>
</div>
);
}

View File

@ -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<string>('');
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<SuccessResponse<MetricRangePayloadProps>> =>
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 (
<div className="all-endpoints-error-state-wrapper">
@ -77,8 +133,27 @@ function TopErrors({
return (
<div className="all-endpoints-container">
<div className="top-errors-dropdown-container">
<div className="endpoint-details-filters-container-dropdown">
<EndPointsDropDown
selectedEndPointName={endPointName}
setSelectedEndPointName={setSelectedEndPointName}
endPointDropDownDataQuery={endPointDropDownDataQuery}
parentContainerDiv=".endpoint-details-filters-container"
/>
</div>
<Tooltip title="Optionally select a specific endpoint to see status message if present">
<Info size={16} color="white" />
</Tooltip>
</div>
<div className="endpoints-table-container">
<div className="endpoints-table-header">Top Errors</div>
<div className="endpoints-table-header">
Top Errors{' '}
<Tooltip title="Shows top 10 errors only when status message is propagated">
<Info size={16} color="white" />
</Tooltip>
</div>
<Table
columns={topErrorsColumnsConfig}
loading={{

View File

@ -2,6 +2,7 @@ import '../DomainDetails.styles.scss';
import { Table, TablePaginationConfig, Typography } from 'antd';
import Skeleton from 'antd/lib/skeleton';
import { QueryParams } from 'constants/query';
import {
dependentServicesColumns,
DependentServicesData,
@ -16,10 +17,15 @@ import ErrorState from './ErrorState';
interface DependentServicesProps {
dependentServicesQuery: UseQueryResult<SuccessResponse<any>, unknown>;
timeRange: {
startTime: number;
endTime: number;
};
}
function DependentServices({
dependentServicesQuery,
timeRange,
}: DependentServicesProps): JSX.Element {
const {
data,
@ -85,6 +91,25 @@ function DependentServices({
</div>
),
}}
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 && (

View File

@ -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<SuccessResponse<MetricRangePayloadProps>> =>
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 (
<div className="all-endpoints-error-state-wrapper">
<ErrorState refetch={refetch} />
</div>
);
}
function DomainMetrics({ domainData }: { domainData: any }): JSX.Element {
return (
<div className="domain-detail-drawer__endpoint">
<div className="domain-details-grid">
@ -35,41 +106,62 @@ function DomainMetrics({ domainData }: { domainData: any }): JSX.Element {
<div className="values-row">
<Typography.Text className="domain-details-metadata-value">
<Tooltip title={domainData.endpointCount}>
<span className="round-metric-tag">{domainData.endpointCount}</span>
</Tooltip>
{isLoading || isRefetching ? (
<Skeleton.Button active size="small" />
) : (
<Tooltip title={formattedDomainMetricsData.endpointCount}>
<span className="round-metric-tag">
{formattedDomainMetricsData.endpointCount}
</span>
</Tooltip>
)}
</Typography.Text>
{/* // update the tooltip as well */}
<Typography.Text className="domain-details-metadata-value">
<Tooltip title={domainData.latency}>
<span className="round-metric-tag">
{(domainData.latency / 1000).toFixed(3)}s
</span>
</Tooltip>
{isLoading || isRefetching ? (
<Skeleton.Button active size="small" />
) : (
<Tooltip title={formattedDomainMetricsData.latency}>
<span className="round-metric-tag">
{(Number(formattedDomainMetricsData.latency) / 1000).toFixed(3)}s
</span>
</Tooltip>
)}
</Typography.Text>
{/* // update the tooltip as well */}
<Typography.Text className="domain-details-metadata-value error-rate">
<Tooltip title={domainData.errorRate}>
<Progress
status="active"
percent={Number(domainData.errorRate.toFixed(1))}
strokeLinecap="butt"
size="small"
strokeColor={((): string => {
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"
/>
</Tooltip>
{isLoading || isRefetching ? (
<Skeleton.Button active size="small" />
) : (
<Tooltip title={formattedDomainMetricsData.errorRate}>
<Progress
status="active"
percent={Number(
Number(formattedDomainMetricsData.errorRate).toFixed(1),
)}
strokeLinecap="butt"
size="small"
strokeColor={((): string => {
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"
/>
</Tooltip>
)}
</Typography.Text>
{/* // update the tooltip as well */}
<Typography.Text className="domain-details-metadata-value">
<Tooltip title={domainData.lastUsed}>
{getLastUsedRelativeTime(domainData.lastUsed)}
</Tooltip>
{isLoading || isRefetching ? (
<Skeleton.Button active size="small" />
) : (
<Tooltip title={formattedDomainMetricsData.lastUsed}>
{formattedDomainMetricsData.lastUsed}
</Tooltip>
)}
</Typography.Text>
</div>
</div>

View File

@ -52,6 +52,10 @@ function EndPointsDropDown({
: (triggerNode): HTMLElement => triggerNode.parentNode as HTMLElement
}
dropdownStyle={dropdownStyle}
allowClear
onClear={(): void => {
setSelectedEndPointName('');
}}
/>
);
}

View File

@ -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',

View File

@ -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 (
<div>
@ -16,10 +18,9 @@ function MetricOverTimeGraph({
<GridCard
widget={widget}
isQueryEnabled
onDragSelect={(): void => {}}
onDragSelect={onDragSelect}
customOnDragSelect={(): void => {}}
start={timeRange.startTime}
end={timeRange.endTime}
customTimeRange={timeRange}
/>
</div>
</Card>

View File

@ -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,
};

View File

@ -60,7 +60,6 @@ function DomainList({ showIP }: { showIP: boolean }): JSX.Element {
aggregateAttribute: {
...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute,
},
queryName: '',
},
],
},

View File

@ -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<DependentServicesData>[] = [
<div className="top-services-item">
<div className="top-services-item-progress">
<div className="top-services-item-key">{serviceData.serviceName}</div>
<div className="top-services-item-count">{serviceData.count}</div>
<div className="top-services-item-count">{serviceData.count} Calls</div>
<div
className="top-services-item-progress-bar"
style={{ width: `${serviceData.percentage}%` }}
@ -3177,10 +3281,13 @@ export const getRateOverTimeWidgetData = (
endPointName: string,
filters: IBuilderQuery['filters'],
): Widgets => {
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,

View File

@ -47,6 +47,7 @@ function GridCardGraph({
start,
end,
analyticsEvent,
customTimeRange,
}: GridCardGraphProps): JSX.Element {
const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>();
@ -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 (

View File

@ -61,6 +61,10 @@ export interface GridCardGraphProps {
start?: number;
end?: number;
analyticsEvent?: string;
customTimeRange?: {
startTime: number;
endTime: number;
};
}
export interface GetGraphVisibilityStateOnLegendClickProps {