mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 09:56:03 +08:00
feat: new dep services top 10 errors localised date picker agrregate domain details etc
This commit is contained in:
parent
8b30e3cc5c
commit
1123a9a93d
@ -54,6 +54,7 @@ export const REACT_QUERY_KEY = {
|
|||||||
|
|
||||||
// API Monitoring Query Keys
|
// API Monitoring Query Keys
|
||||||
GET_DOMAINS_LIST: 'GET_DOMAINS_LIST',
|
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_ENDPOINTS_LIST_BY_DOMAIN: 'GET_ENDPOINTS_LIST_BY_DOMAIN',
|
||||||
GET_TOP_ERRORS_BY_DOMAIN: 'GET_TOP_ERRORS_BY_DOMAIN',
|
GET_TOP_ERRORS_BY_DOMAIN: 'GET_TOP_ERRORS_BY_DOMAIN',
|
||||||
GET_NESTED_ENDPOINTS_LIST: 'GET_NESTED_ENDPOINTS_LIST',
|
GET_NESTED_ENDPOINTS_LIST: 'GET_NESTED_ENDPOINTS_LIST',
|
||||||
|
@ -159,7 +159,7 @@ function AllEndPoints({
|
|||||||
const handleRowClick = (record: EndPointsTableRowData): void => {
|
const handleRowClick = (record: EndPointsTableRowData): void => {
|
||||||
if (groupBy.length === 0) {
|
if (groupBy.length === 0) {
|
||||||
setSelectedEndPointName(record.endpointName); // this will open up the endpoint details tab
|
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', {});
|
logEvent('API Monitoring: Endpoint name row clicked', {});
|
||||||
} else {
|
} else {
|
||||||
handleGroupByRowClick(record); // this will prepare the nested query payload
|
handleGroupByRowClick(record); // this will prepare the nested query payload
|
||||||
|
@ -252,6 +252,9 @@
|
|||||||
border: 1px solid var(--bg-slate-500);
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
|
||||||
.endpoints-table-header {
|
.endpoints-table-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
color: var(--Vanilla-100, #fff);
|
color: var(--Vanilla-100, #fff);
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
@ -392,6 +395,21 @@
|
|||||||
padding-top: 20px;
|
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 {
|
.endpoint-details-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -20,7 +20,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
import AllEndPoints from './AllEndPoints';
|
import AllEndPoints from './AllEndPoints';
|
||||||
import DomainMetrics from './components/DomainMetrics';
|
import DomainMetrics from './components/DomainMetrics';
|
||||||
import { VIEW_TYPES, VIEWS } from './constants';
|
import { VIEW_TYPES, VIEWS } from './constants';
|
||||||
import EndPointDetailsWrapper from './EndPointDetailsWrapper';
|
import EndPointDetails from './EndPointDetails';
|
||||||
import TopErrors from './TopErrors';
|
import TopErrors from './TopErrors';
|
||||||
|
|
||||||
const TimeRangeOffset = 1000000000;
|
const TimeRangeOffset = 1000000000;
|
||||||
@ -156,7 +156,10 @@ function DomainDetails({
|
|||||||
>
|
>
|
||||||
{domainData && (
|
{domainData && (
|
||||||
<>
|
<>
|
||||||
<DomainMetrics domainData={domainData} />
|
<DomainMetrics
|
||||||
|
domainName={domainData.domainName}
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
/>
|
||||||
<div className="views-tabs-container">
|
<div className="views-tabs-container">
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
className="views-tabs"
|
className="views-tabs"
|
||||||
@ -174,13 +177,13 @@ function DomainDetails({
|
|||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
<Radio.Button
|
<Radio.Button
|
||||||
className={
|
className={
|
||||||
selectedView === VIEW_TYPES.ENDPOINT_DETAILS
|
selectedView === VIEW_TYPES.ENDPOINT_STATS
|
||||||
? 'tab selected_view'
|
? 'tab selected_view'
|
||||||
: 'tab'
|
: '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>
|
||||||
<Radio.Button
|
<Radio.Button
|
||||||
className={
|
className={
|
||||||
@ -188,7 +191,7 @@ function DomainDetails({
|
|||||||
}
|
}
|
||||||
value={VIEW_TYPES.TOP_ERRORS}
|
value={VIEW_TYPES.TOP_ERRORS}
|
||||||
>
|
>
|
||||||
<div className="view-title">Top Errors</div>
|
<div className="view-title">Top 10 Errors</div>
|
||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</div>
|
</div>
|
||||||
@ -203,13 +206,14 @@ function DomainDetails({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedView === VIEW_TYPES.ENDPOINT_DETAILS && (
|
{selectedView === VIEW_TYPES.ENDPOINT_STATS && (
|
||||||
<EndPointDetailsWrapper
|
<EndPointDetails
|
||||||
domainName={domainData.domainName}
|
domainName={domainData.domainName}
|
||||||
endPointName={selectedEndPointName}
|
endPointName={selectedEndPointName}
|
||||||
setSelectedEndPointName={setSelectedEndPointName}
|
setSelectedEndPointName={setSelectedEndPointName}
|
||||||
domainListFilters={domainListFilters}
|
domainListFilters={domainListFilters}
|
||||||
timeRange={modalTimeRange}
|
timeRange={modalTimeRange}
|
||||||
|
// handleTimeChange={handleTimeChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -8,6 +8,10 @@ import {
|
|||||||
getRateOverTimeWidgetData,
|
getRateOverTimeWidgetData,
|
||||||
} from 'container/ApiMonitoring/utils';
|
} from 'container/ApiMonitoring/utils';
|
||||||
import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2';
|
import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2';
|
||||||
|
// import {
|
||||||
|
// CustomTimeType,
|
||||||
|
// Time,
|
||||||
|
// } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useQueries } from 'react-query';
|
import { useQueries } from 'react-query';
|
||||||
@ -29,7 +33,8 @@ function EndPointDetails({
|
|||||||
setSelectedEndPointName,
|
setSelectedEndPointName,
|
||||||
domainListFilters,
|
domainListFilters,
|
||||||
timeRange,
|
timeRange,
|
||||||
}: {
|
}: // handleTimeChange,
|
||||||
|
{
|
||||||
domainName: string;
|
domainName: string;
|
||||||
endPointName: string;
|
endPointName: string;
|
||||||
setSelectedEndPointName: (value: string) => void;
|
setSelectedEndPointName: (value: string) => void;
|
||||||
@ -38,6 +43,10 @@ function EndPointDetails({
|
|||||||
startTime: number;
|
startTime: number;
|
||||||
endTime: number;
|
endTime: number;
|
||||||
};
|
};
|
||||||
|
// handleTimeChange: (
|
||||||
|
// interval: Time | CustomTimeType,
|
||||||
|
// dateTimeRange?: [number, number],
|
||||||
|
// ) => void;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { startTime: minTime, endTime: maxTime } = timeRange;
|
const { startTime: minTime, endTime: maxTime } = timeRange;
|
||||||
|
|
||||||
@ -47,6 +56,7 @@ function EndPointDetails({
|
|||||||
op: 'AND',
|
op: 'AND',
|
||||||
items: [],
|
items: [],
|
||||||
});
|
});
|
||||||
|
// [TODO] if endPointName is there then add it to the filters under http.url key
|
||||||
|
|
||||||
// Manually update the query to include the filters
|
// Manually update the query to include the filters
|
||||||
// Because using the hook is causing the global domain
|
// Because using the hook is causing the global domain
|
||||||
@ -78,15 +88,8 @@ function EndPointDetails({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const endPointDetailsQueryPayload = useMemo(
|
const endPointDetailsQueryPayload = useMemo(
|
||||||
() =>
|
() => getEndPointDetailsQueryPayload(domainName, minTime, maxTime, filters),
|
||||||
getEndPointDetailsQueryPayload(
|
[domainName, filters, minTime, maxTime],
|
||||||
domainName,
|
|
||||||
endPointName,
|
|
||||||
minTime,
|
|
||||||
maxTime,
|
|
||||||
filters,
|
|
||||||
),
|
|
||||||
[domainName, endPointName, filters, minTime, maxTime],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const endPointDetailsDataQueries = useQueries(
|
const endPointDetailsDataQueries = useQueries(
|
||||||
@ -141,6 +144,20 @@ function EndPointDetails({
|
|||||||
[domainName, endPointName, filters, domainListFilters],
|
[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 (
|
return (
|
||||||
<div className="endpoint-details-container">
|
<div className="endpoint-details-container">
|
||||||
<div className="endpoint-details-filters-container">
|
<div className="endpoint-details-filters-container">
|
||||||
@ -166,7 +183,9 @@ function EndPointDetails({
|
|||||||
<div className="endpoint-meta-data">
|
<div className="endpoint-meta-data">
|
||||||
<div className="endpoint-meta-data-pill">
|
<div className="endpoint-meta-data-pill">
|
||||||
<div className="endpoint-meta-data-label">Endpoint</div>
|
<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>
|
||||||
<div className="endpoint-meta-data-pill">
|
<div className="endpoint-meta-data-pill">
|
||||||
<div className="endpoint-meta-data-label">Port</div>
|
<div className="endpoint-meta-data-label">Port</div>
|
||||||
@ -177,6 +196,7 @@ function EndPointDetails({
|
|||||||
{!isServicesFilterApplied && (
|
{!isServicesFilterApplied && (
|
||||||
<DependentServices
|
<DependentServices
|
||||||
dependentServicesQuery={endPointDependentServicesDataQuery}
|
dependentServicesQuery={endPointDependentServicesDataQuery}
|
||||||
|
timeRange={timeRange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<StatusCodeBarCharts
|
<StatusCodeBarCharts
|
||||||
@ -191,8 +211,16 @@ function EndPointDetails({
|
|||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
/>
|
/>
|
||||||
<StatusCodeTable endPointStatusCodeDataQuery={endPointStatusCodeDataQuery} />
|
<StatusCodeTable endPointStatusCodeDataQuery={endPointStatusCodeDataQuery} />
|
||||||
<MetricOverTimeGraph widget={rateOverTimeWidget} timeRange={timeRange} />
|
<MetricOverTimeGraph
|
||||||
<MetricOverTimeGraph widget={latencyOverTimeWidget} timeRange={timeRange} />
|
widget={rateOverTimeWidget}
|
||||||
|
timeRange={timeRange}
|
||||||
|
onDragSelect={(): void => {}}
|
||||||
|
/>
|
||||||
|
<MetricOverTimeGraph
|
||||||
|
widget={latencyOverTimeWidget}
|
||||||
|
timeRange={timeRange}
|
||||||
|
onDragSelect={(): void => {}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,24 @@
|
|||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { Spin, Table, Typography } from 'antd';
|
import { Spin, Table, Tooltip, Typography } from 'antd';
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
import { DEFAULT_ENTITY_VERSION, ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import {
|
import {
|
||||||
|
END_POINT_DETAILS_QUERY_KEYS_ARRAY,
|
||||||
formatTopErrorsDataForTable,
|
formatTopErrorsDataForTable,
|
||||||
|
getEndPointDetailsQueryPayload,
|
||||||
getTopErrorsColumnsConfig,
|
getTopErrorsColumnsConfig,
|
||||||
getTopErrorsQueryPayload,
|
getTopErrorsQueryPayload,
|
||||||
TopErrorsResponseRow,
|
TopErrorsResponseRow,
|
||||||
} from 'container/ApiMonitoring/utils';
|
} from 'container/ApiMonitoring/utils';
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
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 { useQueries } from 'react-query';
|
||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
import EndPointsDropDown from './components/EndPointsDropDown';
|
||||||
import ErrorState from './components/ErrorState';
|
import ErrorState from './components/ErrorState';
|
||||||
|
|
||||||
function TopErrors({
|
function TopErrors({
|
||||||
@ -28,9 +33,31 @@ function TopErrors({
|
|||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { startTime: minTime, endTime: maxTime } = timeRange;
|
const { startTime: minTime, endTime: maxTime } = timeRange;
|
||||||
|
|
||||||
|
const [endPointName, setSelectedEndPointName] = useState<string>('');
|
||||||
|
|
||||||
const queryPayloads = useMemo(
|
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
|
// Since only one query here
|
||||||
@ -67,6 +94,35 @@ function TopErrors({
|
|||||||
[topErrorsData],
|
[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) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<div className="all-endpoints-error-state-wrapper">
|
<div className="all-endpoints-error-state-wrapper">
|
||||||
@ -77,8 +133,27 @@ function TopErrors({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="all-endpoints-container">
|
<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-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
|
<Table
|
||||||
columns={topErrorsColumnsConfig}
|
columns={topErrorsColumnsConfig}
|
||||||
loading={{
|
loading={{
|
||||||
|
@ -2,6 +2,7 @@ import '../DomainDetails.styles.scss';
|
|||||||
|
|
||||||
import { Table, TablePaginationConfig, Typography } from 'antd';
|
import { Table, TablePaginationConfig, Typography } from 'antd';
|
||||||
import Skeleton from 'antd/lib/skeleton';
|
import Skeleton from 'antd/lib/skeleton';
|
||||||
|
import { QueryParams } from 'constants/query';
|
||||||
import {
|
import {
|
||||||
dependentServicesColumns,
|
dependentServicesColumns,
|
||||||
DependentServicesData,
|
DependentServicesData,
|
||||||
@ -16,10 +17,15 @@ import ErrorState from './ErrorState';
|
|||||||
|
|
||||||
interface DependentServicesProps {
|
interface DependentServicesProps {
|
||||||
dependentServicesQuery: UseQueryResult<SuccessResponse<any>, unknown>;
|
dependentServicesQuery: UseQueryResult<SuccessResponse<any>, unknown>;
|
||||||
|
timeRange: {
|
||||||
|
startTime: number;
|
||||||
|
endTime: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function DependentServices({
|
function DependentServices({
|
||||||
dependentServicesQuery,
|
dependentServicesQuery,
|
||||||
|
timeRange,
|
||||||
}: DependentServicesProps): JSX.Element {
|
}: DependentServicesProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@ -85,6 +91,25 @@ function DependentServices({
|
|||||||
</div>
|
</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 && (
|
{dependentServicesData.length > 5 && (
|
||||||
|
@ -1,8 +1,79 @@
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Progress, Tooltip, Typography } from 'antd';
|
import { Progress, Skeleton, Tooltip, Typography } from 'antd';
|
||||||
import { getLastUsedRelativeTime } from 'container/ApiMonitoring/utils';
|
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 (
|
return (
|
||||||
<div className="domain-detail-drawer__endpoint">
|
<div className="domain-detail-drawer__endpoint">
|
||||||
<div className="domain-details-grid">
|
<div className="domain-details-grid">
|
||||||
@ -35,41 +106,62 @@ function DomainMetrics({ domainData }: { domainData: any }): JSX.Element {
|
|||||||
|
|
||||||
<div className="values-row">
|
<div className="values-row">
|
||||||
<Typography.Text className="domain-details-metadata-value">
|
<Typography.Text className="domain-details-metadata-value">
|
||||||
<Tooltip title={domainData.endpointCount}>
|
{isLoading || isRefetching ? (
|
||||||
<span className="round-metric-tag">{domainData.endpointCount}</span>
|
<Skeleton.Button active size="small" />
|
||||||
</Tooltip>
|
) : (
|
||||||
|
<Tooltip title={formattedDomainMetricsData.endpointCount}>
|
||||||
|
<span className="round-metric-tag">
|
||||||
|
{formattedDomainMetricsData.endpointCount}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
{/* // update the tooltip as well */}
|
{/* // update the tooltip as well */}
|
||||||
<Typography.Text className="domain-details-metadata-value">
|
<Typography.Text className="domain-details-metadata-value">
|
||||||
<Tooltip title={domainData.latency}>
|
{isLoading || isRefetching ? (
|
||||||
<span className="round-metric-tag">
|
<Skeleton.Button active size="small" />
|
||||||
{(domainData.latency / 1000).toFixed(3)}s
|
) : (
|
||||||
</span>
|
<Tooltip title={formattedDomainMetricsData.latency}>
|
||||||
</Tooltip>
|
<span className="round-metric-tag">
|
||||||
|
{(Number(formattedDomainMetricsData.latency) / 1000).toFixed(3)}s
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
{/* // update the tooltip as well */}
|
{/* // update the tooltip as well */}
|
||||||
<Typography.Text className="domain-details-metadata-value error-rate">
|
<Typography.Text className="domain-details-metadata-value error-rate">
|
||||||
<Tooltip title={domainData.errorRate}>
|
{isLoading || isRefetching ? (
|
||||||
<Progress
|
<Skeleton.Button active size="small" />
|
||||||
status="active"
|
) : (
|
||||||
percent={Number(domainData.errorRate.toFixed(1))}
|
<Tooltip title={formattedDomainMetricsData.errorRate}>
|
||||||
strokeLinecap="butt"
|
<Progress
|
||||||
size="small"
|
status="active"
|
||||||
strokeColor={((): string => {
|
percent={Number(
|
||||||
const errorRatePercent = Number(domainData.errorRate.toFixed(1));
|
Number(formattedDomainMetricsData.errorRate).toFixed(1),
|
||||||
if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
|
)}
|
||||||
if (errorRatePercent >= 60) return Color.BG_AMBER_500;
|
strokeLinecap="butt"
|
||||||
return Color.BG_FOREST_500;
|
size="small"
|
||||||
})()}
|
strokeColor={((): string => {
|
||||||
className="progress-bar"
|
const errorRatePercent = Number(
|
||||||
/>
|
Number(formattedDomainMetricsData.errorRate).toFixed(1),
|
||||||
</Tooltip>
|
);
|
||||||
|
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>
|
</Typography.Text>
|
||||||
{/* // update the tooltip as well */}
|
|
||||||
<Typography.Text className="domain-details-metadata-value">
|
<Typography.Text className="domain-details-metadata-value">
|
||||||
<Tooltip title={domainData.lastUsed}>
|
{isLoading || isRefetching ? (
|
||||||
{getLastUsedRelativeTime(domainData.lastUsed)}
|
<Skeleton.Button active size="small" />
|
||||||
</Tooltip>
|
) : (
|
||||||
|
<Tooltip title={formattedDomainMetricsData.lastUsed}>
|
||||||
|
{formattedDomainMetricsData.lastUsed}
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,6 +52,10 @@ function EndPointsDropDown({
|
|||||||
: (triggerNode): HTMLElement => triggerNode.parentNode as HTMLElement
|
: (triggerNode): HTMLElement => triggerNode.parentNode as HTMLElement
|
||||||
}
|
}
|
||||||
dropdownStyle={dropdownStyle}
|
dropdownStyle={dropdownStyle}
|
||||||
|
allowClear
|
||||||
|
onClear={(): void => {
|
||||||
|
setSelectedEndPointName('');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ function ExpandedRow({
|
|||||||
onRow={(record): { onClick: () => void; className: string } => ({
|
onRow={(record): { onClick: () => void; className: string } => ({
|
||||||
onClick: (): void => {
|
onClick: (): void => {
|
||||||
setSelectedEndPointName(record.endpointName);
|
setSelectedEndPointName(record.endpointName);
|
||||||
setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS);
|
setSelectedView(VIEW_TYPES.ENDPOINT_STATS);
|
||||||
logEvent('API Monitoring: Endpoint name row clicked', {});
|
logEvent('API Monitoring: Endpoint name row clicked', {});
|
||||||
},
|
},
|
||||||
className: 'expanded-clickable-row',
|
className: 'expanded-clickable-row',
|
||||||
|
@ -5,9 +5,11 @@ import { Widgets } from 'types/api/dashboard/getAll';
|
|||||||
function MetricOverTimeGraph({
|
function MetricOverTimeGraph({
|
||||||
widget,
|
widget,
|
||||||
timeRange,
|
timeRange,
|
||||||
|
onDragSelect,
|
||||||
}: {
|
}: {
|
||||||
widget: Widgets;
|
widget: Widgets;
|
||||||
timeRange: { startTime: number; endTime: number };
|
timeRange: { startTime: number; endTime: number };
|
||||||
|
onDragSelect: (start: number, end: number) => void;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -16,10 +18,9 @@ function MetricOverTimeGraph({
|
|||||||
<GridCard
|
<GridCard
|
||||||
widget={widget}
|
widget={widget}
|
||||||
isQueryEnabled
|
isQueryEnabled
|
||||||
onDragSelect={(): void => {}}
|
onDragSelect={onDragSelect}
|
||||||
customOnDragSelect={(): void => {}}
|
customOnDragSelect={(): void => {}}
|
||||||
start={timeRange.startTime}
|
customTimeRange={timeRange}
|
||||||
end={timeRange.endTime}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
export enum VIEWS {
|
export enum VIEWS {
|
||||||
ALL_ENDPOINTS = 'all_endpoints',
|
ALL_ENDPOINTS = 'all_endpoints',
|
||||||
ENDPOINT_DETAILS = 'endpoint_details',
|
ENDPOINT_STATS = 'endpoint_stats',
|
||||||
TOP_ERRORS = 'top_errors',
|
TOP_ERRORS = 'top_errors',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VIEW_TYPES = {
|
export const VIEW_TYPES = {
|
||||||
ALL_ENDPOINTS: VIEWS.ALL_ENDPOINTS,
|
ALL_ENDPOINTS: VIEWS.ALL_ENDPOINTS,
|
||||||
ENDPOINT_DETAILS: VIEWS.ENDPOINT_DETAILS,
|
ENDPOINT_STATS: VIEWS.ENDPOINT_STATS,
|
||||||
TOP_ERRORS: VIEWS.TOP_ERRORS,
|
TOP_ERRORS: VIEWS.TOP_ERRORS,
|
||||||
};
|
};
|
||||||
|
@ -60,7 +60,6 @@ function DomainList({ showIP }: { showIP: boolean }): JSX.Element {
|
|||||||
aggregateAttribute: {
|
aggregateAttribute: {
|
||||||
...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute,
|
...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute,
|
||||||
},
|
},
|
||||||
queryName: '',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -333,6 +333,278 @@ export const formatDataForTable = (
|
|||||||
).toISOString(), // Convert from nanoseconds to milliseconds
|
).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
|
// Rename this to a proper name
|
||||||
const defaultGroupBy = [
|
const defaultGroupBy = [
|
||||||
{
|
{
|
||||||
@ -637,6 +909,7 @@ export const getTopErrorsQueryPayload = (
|
|||||||
domainName: string,
|
domainName: string,
|
||||||
start: number,
|
start: number,
|
||||||
end: number,
|
end: number,
|
||||||
|
filters: IBuilderQuery['filters'],
|
||||||
): GetQueryResultsProps[] => [
|
): GetQueryResultsProps[] => [
|
||||||
{
|
{
|
||||||
selectedTime: 'GLOBAL_TIME',
|
selectedTime: 'GLOBAL_TIME',
|
||||||
@ -714,6 +987,7 @@ export const getTopErrorsQueryPayload = (
|
|||||||
op: '=',
|
op: '=',
|
||||||
value: domainName,
|
value: domainName,
|
||||||
},
|
},
|
||||||
|
...filters.items,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
expression: 'A',
|
expression: 'A',
|
||||||
@ -1192,7 +1466,6 @@ export const createFiltersForSelectedRowData = (
|
|||||||
// Sixth query payload for endpoint response status code latency bar chart
|
// Sixth query payload for endpoint response status code latency bar chart
|
||||||
export const getEndPointDetailsQueryPayload = (
|
export const getEndPointDetailsQueryPayload = (
|
||||||
domainName: string,
|
domainName: string,
|
||||||
endPointName: string,
|
|
||||||
start: number,
|
start: number,
|
||||||
end: number,
|
end: number,
|
||||||
filters: IBuilderQuery['filters'],
|
filters: IBuilderQuery['filters'],
|
||||||
@ -1218,19 +1491,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'A',
|
expression: 'A',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: '874562e1',
|
||||||
key: {
|
key: {
|
||||||
@ -1288,19 +1548,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'B',
|
expression: 'B',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: '0c5564e0',
|
||||||
key: {
|
key: {
|
||||||
@ -1358,19 +1605,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'C',
|
expression: 'C',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: '0d656701',
|
||||||
key: {
|
key: {
|
||||||
@ -1440,19 +1674,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'D',
|
expression: 'D',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: '918f5b99',
|
||||||
key: {
|
key: {
|
||||||
@ -1510,19 +1731,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'E',
|
expression: 'E',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: 'b355d1aa',
|
||||||
key: {
|
key: {
|
||||||
@ -1634,19 +1842,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
op: '=',
|
op: '=',
|
||||||
value: domainName,
|
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',
|
id: '212678b9',
|
||||||
key: {
|
key: {
|
||||||
@ -1713,19 +1908,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
op: '=',
|
op: '=',
|
||||||
value: domainName,
|
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',
|
id: '212678b9',
|
||||||
key: {
|
key: {
|
||||||
@ -1909,19 +2091,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'A',
|
expression: 'A',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: 'b78ff216',
|
||||||
key: {
|
key: {
|
||||||
@ -1988,19 +2157,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'B',
|
expression: 'B',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: 'a9024472',
|
||||||
key: {
|
key: {
|
||||||
@ -2066,19 +2222,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'C',
|
expression: 'C',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: '1b6c062d',
|
||||||
key: {
|
key: {
|
||||||
@ -2145,19 +2288,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'D',
|
expression: 'D',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: 'd14792a8',
|
||||||
key: {
|
key: {
|
||||||
@ -2290,19 +2420,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
op: '=',
|
op: '=',
|
||||||
value: domainName,
|
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',
|
id: '212678b9',
|
||||||
key: {
|
key: {
|
||||||
@ -2390,19 +2507,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
expression: 'A',
|
expression: 'A',
|
||||||
filters: {
|
filters: {
|
||||||
items: [
|
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',
|
id: 'aae93366',
|
||||||
key: {
|
key: {
|
||||||
@ -2787,7 +2891,7 @@ export const dependentServicesColumns: ColumnType<DependentServicesData>[] = [
|
|||||||
<div className="top-services-item">
|
<div className="top-services-item">
|
||||||
<div className="top-services-item-progress">
|
<div className="top-services-item-progress">
|
||||||
<div className="top-services-item-key">{serviceData.serviceName}</div>
|
<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
|
<div
|
||||||
className="top-services-item-progress-bar"
|
className="top-services-item-progress-bar"
|
||||||
style={{ width: `${serviceData.percentage}%` }}
|
style={{ width: `${serviceData.percentage}%` }}
|
||||||
@ -3177,10 +3281,13 @@ export const getRateOverTimeWidgetData = (
|
|||||||
endPointName: string,
|
endPointName: string,
|
||||||
filters: IBuilderQuery['filters'],
|
filters: IBuilderQuery['filters'],
|
||||||
): Widgets => {
|
): Widgets => {
|
||||||
const { endpoint, port } = extractPortAndEndpoint(endPointName);
|
let legend = domainName;
|
||||||
const legend = `${
|
if (endPointName) {
|
||||||
port !== '-' && port !== 'n/a' ? `${port}:` : ''
|
const { endpoint, port } = extractPortAndEndpoint(endPointName);
|
||||||
}${endpoint}`;
|
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
||||||
|
legend = `${port !== '-' && port !== 'n/a' ? `${port}:` : ''}${endpoint}`;
|
||||||
|
}
|
||||||
|
|
||||||
return getWidgetQueryBuilder(
|
return getWidgetQueryBuilder(
|
||||||
getWidgetQuery({
|
getWidgetQuery({
|
||||||
title: 'Rate Over Time',
|
title: 'Rate Over Time',
|
||||||
@ -3213,34 +3320,12 @@ export const getRateOverTimeWidgetData = (
|
|||||||
op: '=',
|
op: '=',
|
||||||
value: domainName,
|
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,
|
...filters.items,
|
||||||
],
|
],
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
},
|
},
|
||||||
functions: [],
|
functions: [],
|
||||||
groupBy: [
|
groupBy: [],
|
||||||
{
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
id: 'http.url--string--tag--false',
|
|
||||||
isColumn: false,
|
|
||||||
isJSON: false,
|
|
||||||
key: 'http.url',
|
|
||||||
type: 'tag',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
having: [],
|
having: [],
|
||||||
legend,
|
legend,
|
||||||
limit: null,
|
limit: null,
|
||||||
@ -3262,8 +3347,13 @@ export const getLatencyOverTimeWidgetData = (
|
|||||||
endPointName: string,
|
endPointName: string,
|
||||||
filters: IBuilderQuery['filters'],
|
filters: IBuilderQuery['filters'],
|
||||||
): Widgets => {
|
): Widgets => {
|
||||||
const { endpoint, port } = extractPortAndEndpoint(endPointName);
|
let legend = domainName;
|
||||||
const legend = `${port}:${endpoint}`;
|
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(
|
return getWidgetQueryBuilder(
|
||||||
getWidgetQuery({
|
getWidgetQuery({
|
||||||
title: 'Latency Over Time',
|
title: 'Latency Over Time',
|
||||||
@ -3297,34 +3387,12 @@ export const getLatencyOverTimeWidgetData = (
|
|||||||
op: '=',
|
op: '=',
|
||||||
value: domainName,
|
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,
|
...filters.items,
|
||||||
],
|
],
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
},
|
},
|
||||||
functions: [],
|
functions: [],
|
||||||
groupBy: [
|
groupBy: [],
|
||||||
{
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
id: 'http.url--string--tag--false',
|
|
||||||
isColumn: false,
|
|
||||||
isJSON: false,
|
|
||||||
key: 'http.url',
|
|
||||||
type: 'tag',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
having: [],
|
having: [],
|
||||||
legend,
|
legend,
|
||||||
limit: null,
|
limit: null,
|
||||||
|
@ -47,6 +47,7 @@ function GridCardGraph({
|
|||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
analyticsEvent,
|
analyticsEvent,
|
||||||
|
customTimeRange,
|
||||||
}: GridCardGraphProps): JSX.Element {
|
}: GridCardGraphProps): JSX.Element {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
@ -130,6 +131,8 @@ function GridCardGraph({
|
|||||||
variables: getDashboardVariables(variables),
|
variables: getDashboardVariables(variables),
|
||||||
fillGaps: widget.fillSpans,
|
fillGaps: widget.fillSpans,
|
||||||
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
|
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
|
||||||
|
start: customTimeRange?.startTime || start,
|
||||||
|
end: customTimeRange?.endTime || end,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
updatedQuery.builder.queryData[0].pageSize = 10;
|
updatedQuery.builder.queryData[0].pageSize = 10;
|
||||||
@ -149,6 +152,8 @@ function GridCardGraph({
|
|||||||
initialDataSource === DataSource.TRACES && widget.selectedTracesFields,
|
initialDataSource === DataSource.TRACES && widget.selectedTracesFields,
|
||||||
},
|
},
|
||||||
fillGaps: widget.fillSpans,
|
fillGaps: widget.fillSpans,
|
||||||
|
start: customTimeRange?.startTime || start,
|
||||||
|
end: customTimeRange?.endTime || end,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -187,8 +192,8 @@ function GridCardGraph({
|
|||||||
variables: getDashboardVariables(variables),
|
variables: getDashboardVariables(variables),
|
||||||
selectedTime: widget.timePreferance || 'GLOBAL_TIME',
|
selectedTime: widget.timePreferance || 'GLOBAL_TIME',
|
||||||
globalSelectedInterval,
|
globalSelectedInterval,
|
||||||
start,
|
start: customTimeRange?.startTime || start,
|
||||||
end,
|
end: customTimeRange?.endTime || end,
|
||||||
},
|
},
|
||||||
version || DEFAULT_ENTITY_VERSION,
|
version || DEFAULT_ENTITY_VERSION,
|
||||||
{
|
{
|
||||||
@ -202,6 +207,9 @@ function GridCardGraph({
|
|||||||
widget.timePreferance,
|
widget.timePreferance,
|
||||||
widget.fillSpans,
|
widget.fillSpans,
|
||||||
requestData,
|
requestData,
|
||||||
|
...(customTimeRange && customTimeRange.startTime && customTimeRange.endTime
|
||||||
|
? [customTimeRange.startTime, customTimeRange.endTime]
|
||||||
|
: []),
|
||||||
],
|
],
|
||||||
retry(failureCount, error): boolean {
|
retry(failureCount, error): boolean {
|
||||||
if (
|
if (
|
||||||
|
@ -61,6 +61,10 @@ export interface GridCardGraphProps {
|
|||||||
start?: number;
|
start?: number;
|
||||||
end?: number;
|
end?: number;
|
||||||
analyticsEvent?: string;
|
analyticsEvent?: string;
|
||||||
|
customTimeRange?: {
|
||||||
|
startTime: number;
|
||||||
|
endTime: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetGraphVisibilityStateOnLegendClickProps {
|
export interface GetGraphVisibilityStateOnLegendClickProps {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user