mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 01:36:08 +08:00
feat: api monitoring feedback - 2 (#7432)
* feat: new dropdown styles * fix: added new tag * feat: added endpoint name and port in endpoint details * feat: endpoint details feedback * feat: analytics added * fix: title fixed * fix: domain list breaking for non available data * feat: added third party api feature flag * fix: console removed * feat: added traces corelation in api monitoring charts * feat: added customondragselect in grid card full view to handle breaking flow * fix: minor failsafes added: * fix: minor ux fix * feat: incorporated pr comments - 0
This commit is contained in:
parent
62c033ccf8
commit
0b7cd4c1a7
@ -68,5 +68,6 @@
|
|||||||
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
|
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
|
||||||
"METRICS_EXPLORER": "SigNoz | Metrics Explorer",
|
"METRICS_EXPLORER": "SigNoz | Metrics Explorer",
|
||||||
"METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer",
|
"METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer",
|
||||||
"METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer"
|
"METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer",
|
||||||
|
"API_MONITORING": "SigNoz | API Monitoring"
|
||||||
}
|
}
|
||||||
|
@ -25,5 +25,6 @@ export enum FeatureKeys {
|
|||||||
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
|
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
|
||||||
AWS_INTEGRATION = 'AWS_INTEGRATION',
|
AWS_INTEGRATION = 'AWS_INTEGRATION',
|
||||||
ONBOARDING_V3 = 'ONBOARDING_V3',
|
ONBOARDING_V3 = 'ONBOARDING_V3',
|
||||||
|
THIRD_PARTY_API = 'THIRD_PARTY_API',
|
||||||
TRACE_FUNNELS = 'TRACE_FUNNELS',
|
TRACE_FUNNELS = 'TRACE_FUNNELS',
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { Select, Spin, Table, Typography } from 'antd';
|
import { Select, Spin, Table, Typography } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import {
|
import {
|
||||||
@ -151,6 +152,7 @@ function AllEndPoints({
|
|||||||
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_DETAILS);
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
@ -392,6 +392,39 @@
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
|
|
||||||
|
.endpoint-meta-data {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
.endpoint-meta-data-pill {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--bg-slate-300);
|
||||||
|
width: fit-content;
|
||||||
|
.endpoint-meta-data-label {
|
||||||
|
display: flex;
|
||||||
|
padding: 6px 8px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
border-right: 1px solid var(--bg-slate-300);
|
||||||
|
color: var(--text-vanilla-100);
|
||||||
|
background: var(--bg-slate-500);
|
||||||
|
height: calc(100% - 12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-meta-data-value {
|
||||||
|
display: flex;
|
||||||
|
padding: 6px 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: var(--text-vanilla-400);
|
||||||
|
background: var(--bg-slate-400);
|
||||||
|
height: calc(100% - 12px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.endpoint-details-filters-container {
|
.endpoint-details-filters-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -405,6 +438,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-select-item,
|
||||||
|
.ant-select-item-option-content {
|
||||||
|
flex: auto;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.status-code-table-container {
|
.status-code-table-container {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid var(--bg-slate-500);
|
border: 1px solid var(--bg-slate-500);
|
||||||
@ -809,6 +849,13 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-select-item,
|
||||||
|
.ant-select-item-option-content {
|
||||||
|
flex: auto;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
@ -917,6 +964,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.endpoint-meta-data {
|
||||||
|
.endpoint-meta-data-pill {
|
||||||
|
.endpoint-meta-data-label {
|
||||||
|
color: var(--text-ink-300);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-meta-data-value {
|
||||||
|
color: var(--text-ink-300);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status-code-table-container {
|
.status-code-table-container {
|
||||||
.ant-table {
|
.ant-table {
|
||||||
.ant-table-thead > tr > th {
|
.ant-table-thead > tr > th {
|
||||||
|
@ -19,12 +19,14 @@ function DomainDetails({
|
|||||||
selectedDomainIndex,
|
selectedDomainIndex,
|
||||||
setSelectedDomainIndex,
|
setSelectedDomainIndex,
|
||||||
domainListLength,
|
domainListLength,
|
||||||
|
domainListFilters,
|
||||||
}: {
|
}: {
|
||||||
domainData: any;
|
domainData: any;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
selectedDomainIndex: number;
|
selectedDomainIndex: number;
|
||||||
setSelectedDomainIndex: (index: number) => void;
|
setSelectedDomainIndex: (index: number) => void;
|
||||||
domainListLength: number;
|
domainListLength: number;
|
||||||
|
domainListFilters: IBuilderQuery['filters'];
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.ALL_ENDPOINTS);
|
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.ALL_ENDPOINTS);
|
||||||
const [selectedEndPointName, setSelectedEndPointName] = useState<string>('');
|
const [selectedEndPointName, setSelectedEndPointName] = useState<string>('');
|
||||||
@ -132,6 +134,7 @@ function DomainDetails({
|
|||||||
domainName={domainData.domainName}
|
domainName={domainData.domainName}
|
||||||
endPointName={selectedEndPointName}
|
endPointName={selectedEndPointName}
|
||||||
setSelectedEndPointName={setSelectedEndPointName}
|
setSelectedEndPointName={setSelectedEndPointName}
|
||||||
|
domainListFilters={domainListFilters}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -2,7 +2,10 @@ import { ENTITY_VERSION_V4 } from 'constants/app';
|
|||||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||||
import {
|
import {
|
||||||
END_POINT_DETAILS_QUERY_KEYS_ARRAY,
|
END_POINT_DETAILS_QUERY_KEYS_ARRAY,
|
||||||
|
extractPortAndEndpoint,
|
||||||
getEndPointDetailsQueryPayload,
|
getEndPointDetailsQueryPayload,
|
||||||
|
getLatencyOverTimeWidgetData,
|
||||||
|
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 { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||||
@ -27,10 +30,12 @@ function EndPointDetails({
|
|||||||
domainName,
|
domainName,
|
||||||
endPointName,
|
endPointName,
|
||||||
setSelectedEndPointName,
|
setSelectedEndPointName,
|
||||||
|
domainListFilters,
|
||||||
}: {
|
}: {
|
||||||
domainName: string;
|
domainName: string;
|
||||||
endPointName: string;
|
endPointName: string;
|
||||||
setSelectedEndPointName: (value: string) => void;
|
setSelectedEndPointName: (value: string) => void;
|
||||||
|
domainListFilters: IBuilderQuery['filters'];
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
@ -101,8 +106,6 @@ function EndPointDetails({
|
|||||||
const [
|
const [
|
||||||
endPointMetricsDataQuery,
|
endPointMetricsDataQuery,
|
||||||
endPointStatusCodeDataQuery,
|
endPointStatusCodeDataQuery,
|
||||||
endPointRateOverTimeDataQuery,
|
|
||||||
endPointLatencyOverTimeDataQuery,
|
|
||||||
endPointDropDownDataQuery,
|
endPointDropDownDataQuery,
|
||||||
endPointDependentServicesDataQuery,
|
endPointDependentServicesDataQuery,
|
||||||
endPointStatusCodeBarChartsDataQuery,
|
endPointStatusCodeBarChartsDataQuery,
|
||||||
@ -115,12 +118,29 @@ function EndPointDetails({
|
|||||||
endPointDetailsDataQueries[3],
|
endPointDetailsDataQueries[3],
|
||||||
endPointDetailsDataQueries[4],
|
endPointDetailsDataQueries[4],
|
||||||
endPointDetailsDataQueries[5],
|
endPointDetailsDataQueries[5],
|
||||||
endPointDetailsDataQueries[6],
|
|
||||||
endPointDetailsDataQueries[7],
|
|
||||||
],
|
],
|
||||||
[endPointDetailsDataQueries],
|
[endPointDetailsDataQueries],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { endpoint, port } = useMemo(
|
||||||
|
() => extractPortAndEndpoint(endPointName),
|
||||||
|
[endPointName],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [rateOverTimeWidget, latencyOverTimeWidget] = useMemo(
|
||||||
|
() => [
|
||||||
|
getRateOverTimeWidgetData(domainName, endPointName, {
|
||||||
|
items: [...domainListFilters.items, ...filters.items],
|
||||||
|
op: filters.op,
|
||||||
|
}),
|
||||||
|
getLatencyOverTimeWidgetData(domainName, endPointName, {
|
||||||
|
items: [...domainListFilters.items, ...filters.items],
|
||||||
|
op: filters.op,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[domainName, endPointName, filters, domainListFilters],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="endpoint-details-container">
|
<div className="endpoint-details-container">
|
||||||
<div className="endpoint-details-filters-container">
|
<div className="endpoint-details-filters-container">
|
||||||
@ -129,6 +149,8 @@ function EndPointDetails({
|
|||||||
selectedEndPointName={endPointName}
|
selectedEndPointName={endPointName}
|
||||||
setSelectedEndPointName={setSelectedEndPointName}
|
setSelectedEndPointName={setSelectedEndPointName}
|
||||||
endPointDropDownDataQuery={endPointDropDownDataQuery}
|
endPointDropDownDataQuery={endPointDropDownDataQuery}
|
||||||
|
parentContainerDiv=".endpoint-details-filters-container"
|
||||||
|
dropdownStyle={{ width: 'calc(100% - 36px)' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="endpoint-details-filters-container-search">
|
<div className="endpoint-details-filters-container-search">
|
||||||
@ -141,6 +163,16 @@ function EndPointDetails({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
||||||
|
<div className="endpoint-meta-data-pill">
|
||||||
|
<div className="endpoint-meta-data-label">Port</div>
|
||||||
|
<div className="endpoint-meta-data-value">{port || '-'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<EndPointMetrics endPointMetricsDataQuery={endPointMetricsDataQuery} />
|
<EndPointMetrics endPointMetricsDataQuery={endPointMetricsDataQuery} />
|
||||||
{!isServicesFilterApplied && (
|
{!isServicesFilterApplied && (
|
||||||
<DependentServices
|
<DependentServices
|
||||||
@ -152,18 +184,14 @@ function EndPointDetails({
|
|||||||
endPointStatusCodeLatencyBarChartsDataQuery={
|
endPointStatusCodeLatencyBarChartsDataQuery={
|
||||||
endPointStatusCodeLatencyBarChartsDataQuery
|
endPointStatusCodeLatencyBarChartsDataQuery
|
||||||
}
|
}
|
||||||
|
domainName={domainName}
|
||||||
|
endPointName={endPointName}
|
||||||
|
domainListFilters={domainListFilters}
|
||||||
|
filters={filters}
|
||||||
/>
|
/>
|
||||||
<StatusCodeTable endPointStatusCodeDataQuery={endPointStatusCodeDataQuery} />
|
<StatusCodeTable endPointStatusCodeDataQuery={endPointStatusCodeDataQuery} />
|
||||||
<MetricOverTimeGraph
|
<MetricOverTimeGraph widget={rateOverTimeWidget} />
|
||||||
metricOverTimeDataQuery={endPointRateOverTimeDataQuery}
|
<MetricOverTimeGraph widget={latencyOverTimeWidget} />
|
||||||
widgetInfoIndex={0}
|
|
||||||
endPointName={endPointName}
|
|
||||||
/>
|
|
||||||
<MetricOverTimeGraph
|
|
||||||
metricOverTimeDataQuery={endPointLatencyOverTimeDataQuery}
|
|
||||||
widgetInfoIndex={1}
|
|
||||||
endPointName={endPointName}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { useSelector } from 'react-redux';
|
|||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
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 { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
import EndPointDetailsZeroState from './components/EndPointDetailsZeroState';
|
import EndPointDetailsZeroState from './components/EndPointDetailsZeroState';
|
||||||
@ -17,10 +18,12 @@ function EndPointDetailsWrapper({
|
|||||||
domainName,
|
domainName,
|
||||||
endPointName,
|
endPointName,
|
||||||
setSelectedEndPointName,
|
setSelectedEndPointName,
|
||||||
|
domainListFilters,
|
||||||
}: {
|
}: {
|
||||||
domainName: string;
|
domainName: string;
|
||||||
endPointName: string;
|
endPointName: string;
|
||||||
setSelectedEndPointName: (value: string) => void;
|
setSelectedEndPointName: (value: string) => void;
|
||||||
|
domainListFilters: IBuilderQuery['filters'];
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
@ -69,6 +72,7 @@ function EndPointDetailsWrapper({
|
|||||||
domainName={domainName}
|
domainName={domainName}
|
||||||
endPointName={endPointName}
|
endPointName={endPointName}
|
||||||
setSelectedEndPointName={setSelectedEndPointName}
|
setSelectedEndPointName={setSelectedEndPointName}
|
||||||
|
domainListFilters={domainListFilters}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,8 @@ function EndPointDetailsZeroState({
|
|||||||
<EndPointsDropDown
|
<EndPointsDropDown
|
||||||
setSelectedEndPointName={setSelectedEndPointName}
|
setSelectedEndPointName={setSelectedEndPointName}
|
||||||
endPointDropDownDataQuery={endPointDropDownDataQuery}
|
endPointDropDownDataQuery={endPointDropDownDataQuery}
|
||||||
|
parentContainerDiv=".end-point-details-zero-state-wrapper"
|
||||||
|
dropdownStyle={{ width: '60%' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,7 +70,7 @@ function EndPointMetrics({
|
|||||||
<Skeleton.Button active size="small" />
|
<Skeleton.Button active size="small" />
|
||||||
) : (
|
) : (
|
||||||
<Tooltip title={metricsData?.rate}>
|
<Tooltip title={metricsData?.rate}>
|
||||||
<span className="round-metric-tag">{metricsData?.rate}/sec</span>
|
<span className="round-metric-tag">{metricsData?.rate} ops/sec</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
@ -8,16 +8,22 @@ interface EndPointsDropDownProps {
|
|||||||
selectedEndPointName?: string;
|
selectedEndPointName?: string;
|
||||||
setSelectedEndPointName: (value: string) => void;
|
setSelectedEndPointName: (value: string) => void;
|
||||||
endPointDropDownDataQuery: UseQueryResult<SuccessResponse<any>, unknown>;
|
endPointDropDownDataQuery: UseQueryResult<SuccessResponse<any>, unknown>;
|
||||||
|
parentContainerDiv?: string;
|
||||||
|
dropdownStyle?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
selectedEndPointName: '',
|
selectedEndPointName: '',
|
||||||
|
parentContainerDiv: '',
|
||||||
|
dropdownStyle: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function EndPointsDropDown({
|
function EndPointsDropDown({
|
||||||
selectedEndPointName,
|
selectedEndPointName,
|
||||||
setSelectedEndPointName,
|
setSelectedEndPointName,
|
||||||
endPointDropDownDataQuery,
|
endPointDropDownDataQuery,
|
||||||
|
parentContainerDiv,
|
||||||
|
dropdownStyle,
|
||||||
}: EndPointsDropDownProps): JSX.Element {
|
}: EndPointsDropDownProps): JSX.Element {
|
||||||
const { data, isLoading, isFetching } = endPointDropDownDataQuery;
|
const { data, isLoading, isFetching } = endPointDropDownDataQuery;
|
||||||
|
|
||||||
@ -39,6 +45,13 @@ function EndPointsDropDown({
|
|||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
options={formattedData}
|
options={formattedData}
|
||||||
|
getPopupContainer={
|
||||||
|
parentContainerDiv
|
||||||
|
? (): HTMLElement =>
|
||||||
|
document.querySelector(parentContainerDiv) as HTMLElement
|
||||||
|
: (triggerNode): HTMLElement => triggerNode.parentNode as HTMLElement
|
||||||
|
}
|
||||||
|
dropdownStyle={dropdownStyle}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { Spin, Table } from 'antd';
|
import { Spin, Table } from 'antd';
|
||||||
import { ColumnType } from 'antd/lib/table';
|
import { ColumnType } from 'antd/lib/table';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import {
|
import {
|
||||||
@ -114,6 +115,7 @@ function ExpandedRow({
|
|||||||
onClick: (): void => {
|
onClick: (): void => {
|
||||||
setSelectedEndPointName(record.endpointName);
|
setSelectedEndPointName(record.endpointName);
|
||||||
setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS);
|
setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS);
|
||||||
|
logEvent('API Monitoring: Endpoint name row clicked', {});
|
||||||
},
|
},
|
||||||
className: 'expanded-clickable-row',
|
className: 'expanded-clickable-row',
|
||||||
})}
|
})}
|
||||||
|
@ -1,110 +1,18 @@
|
|||||||
import { Card, Skeleton, Typography } from 'antd';
|
import { Card } from 'antd';
|
||||||
import cx from 'classnames';
|
import GridCard from 'container/GridCardLayout/GridCard';
|
||||||
import Uplot from 'components/Uplot';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import {
|
|
||||||
apiWidgetInfo,
|
|
||||||
extractPortAndEndpoint,
|
|
||||||
getFormattedChartData,
|
|
||||||
} from 'container/ApiMonitoring/utils';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
||||||
import { useResizeObserver } from 'hooks/useDimensions';
|
|
||||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
|
||||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
|
||||||
import { useCallback, useMemo, useRef } from 'react';
|
|
||||||
import { UseQueryResult } from 'react-query';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import { SuccessResponse } from 'types/api';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
import { Options } from 'uplot';
|
|
||||||
|
|
||||||
import ErrorState from './ErrorState';
|
|
||||||
|
|
||||||
function MetricOverTimeGraph({
|
|
||||||
metricOverTimeDataQuery,
|
|
||||||
widgetInfoIndex,
|
|
||||||
endPointName,
|
|
||||||
}: {
|
|
||||||
metricOverTimeDataQuery: UseQueryResult<SuccessResponse<any>, unknown>;
|
|
||||||
widgetInfoIndex: number;
|
|
||||||
endPointName: string;
|
|
||||||
}): JSX.Element {
|
|
||||||
const { data } = metricOverTimeDataQuery;
|
|
||||||
|
|
||||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
|
||||||
(state) => state.globalTime,
|
|
||||||
);
|
|
||||||
|
|
||||||
const graphRef = useRef<HTMLDivElement>(null);
|
|
||||||
const dimensions = useResizeObserver(graphRef);
|
|
||||||
|
|
||||||
const { endpoint } = extractPortAndEndpoint(endPointName);
|
|
||||||
|
|
||||||
const formattedChartData = useMemo(
|
|
||||||
() => getFormattedChartData(data?.payload, [endpoint]),
|
|
||||||
[data?.payload, endpoint],
|
|
||||||
);
|
|
||||||
|
|
||||||
const chartData = useMemo(() => getUPlotChartData(formattedChartData), [
|
|
||||||
formattedChartData,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
|
||||||
|
|
||||||
const options = useMemo(
|
|
||||||
() =>
|
|
||||||
getUPlotChartOptions({
|
|
||||||
apiResponse: formattedChartData,
|
|
||||||
isDarkMode,
|
|
||||||
dimensions,
|
|
||||||
yAxisUnit: apiWidgetInfo[widgetInfoIndex].yAxisUnit,
|
|
||||||
softMax: null,
|
|
||||||
softMin: null,
|
|
||||||
minTimeScale: Math.floor(minTime / 1e9),
|
|
||||||
maxTimeScale: Math.floor(maxTime / 1e9),
|
|
||||||
panelType: PANEL_TYPES.TIME_SERIES,
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
formattedChartData,
|
|
||||||
minTime,
|
|
||||||
maxTime,
|
|
||||||
widgetInfoIndex,
|
|
||||||
dimensions,
|
|
||||||
isDarkMode,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderCardContent = useCallback(
|
|
||||||
(query: UseQueryResult<SuccessResponse<any>, unknown>): JSX.Element => {
|
|
||||||
if (query.isLoading) {
|
|
||||||
return <Skeleton />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.error) {
|
|
||||||
return <ErrorState refetch={query.refetch} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cx('chart-container', {
|
|
||||||
'no-data-container':
|
|
||||||
!query.isLoading && !query?.data?.payload?.data?.result?.length,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Uplot options={options as Options} data={chartData} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[options, chartData],
|
|
||||||
);
|
|
||||||
|
|
||||||
|
function MetricOverTimeGraph({ widget }: { widget: Widgets }): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Card bordered className="endpoint-details-card">
|
<Card bordered className="endpoint-details-card">
|
||||||
<Typography.Text>{apiWidgetInfo[widgetInfoIndex].title}</Typography.Text>
|
<div className="graph-container">
|
||||||
<div className="graph-container" ref={graphRef}>
|
<GridCard
|
||||||
{renderCardContent(metricOverTimeDataQuery)}
|
widget={widget}
|
||||||
|
isQueryEnabled
|
||||||
|
onDragSelect={(): void => {}}
|
||||||
|
customOnDragSelect={(): void => {}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Card, Skeleton, Typography } from 'antd';
|
import { Button, Card, Skeleton, Typography } from 'antd';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
import { useGetGraphCustomSeries } from 'components/CeleryTask/useGetGraphCustomSeries';
|
||||||
|
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
||||||
import Uplot from 'components/Uplot';
|
import Uplot from 'components/Uplot';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import {
|
import {
|
||||||
|
getCustomFiltersForBarChart,
|
||||||
getFormattedEndPointStatusCodeChartData,
|
getFormattedEndPointStatusCodeChartData,
|
||||||
|
getStatusCodeBarChartWidgetData,
|
||||||
statusCodeWidgetInfo,
|
statusCodeWidgetInfo,
|
||||||
} from 'container/ApiMonitoring/utils';
|
} from 'container/ApiMonitoring/utils';
|
||||||
|
import { handleGraphClick } from 'container/GridCardLayout/GridCard/utils';
|
||||||
|
import { useGraphClickToShowButton } from 'container/GridCardLayout/useGraphClickToShowButton';
|
||||||
|
import useNavigateToExplorerPages from 'container/GridCardLayout/useNavigateToExplorerPages';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useResizeObserver } from 'hooks/useDimensions';
|
import { useResizeObserver } from 'hooks/useDimensions';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
@ -15,6 +24,8 @@ import { UseQueryResult } from 'react-query';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { Options } from 'uplot';
|
import { Options } from 'uplot';
|
||||||
|
|
||||||
@ -23,6 +34,10 @@ import ErrorState from './ErrorState';
|
|||||||
function StatusCodeBarCharts({
|
function StatusCodeBarCharts({
|
||||||
endPointStatusCodeBarChartsDataQuery,
|
endPointStatusCodeBarChartsDataQuery,
|
||||||
endPointStatusCodeLatencyBarChartsDataQuery,
|
endPointStatusCodeLatencyBarChartsDataQuery,
|
||||||
|
domainName,
|
||||||
|
endPointName,
|
||||||
|
domainListFilters,
|
||||||
|
filters,
|
||||||
}: {
|
}: {
|
||||||
endPointStatusCodeBarChartsDataQuery: UseQueryResult<
|
endPointStatusCodeBarChartsDataQuery: UseQueryResult<
|
||||||
SuccessResponse<any>,
|
SuccessResponse<any>,
|
||||||
@ -32,6 +47,10 @@ function StatusCodeBarCharts({
|
|||||||
SuccessResponse<any>,
|
SuccessResponse<any>,
|
||||||
unknown
|
unknown
|
||||||
>;
|
>;
|
||||||
|
domainName: string;
|
||||||
|
endPointName: string;
|
||||||
|
domainListFilters: IBuilderQuery['filters'];
|
||||||
|
filters: IBuilderQuery['filters'];
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
// 0 : Status Code Count
|
// 0 : Status Code Count
|
||||||
// 1 : Status Code Latency
|
// 1 : Status Code Latency
|
||||||
@ -85,6 +104,72 @@ function StatusCodeBarCharts({
|
|||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const graphClick = useGraphClickToShowButton({
|
||||||
|
graphRef,
|
||||||
|
isButtonEnabled: true,
|
||||||
|
buttonClassName: 'view-onclick-show-button',
|
||||||
|
});
|
||||||
|
|
||||||
|
const navigateToExplorer = useNavigateToExplorer();
|
||||||
|
|
||||||
|
const navigateToExplorerPages = useNavigateToExplorerPages();
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
const { getCustomSeries } = useGetGraphCustomSeries({
|
||||||
|
isDarkMode,
|
||||||
|
drawStyle: 'bars',
|
||||||
|
colorMapping: {
|
||||||
|
'200-299': Color.BG_FOREST_500,
|
||||||
|
'300-399': Color.BG_AMBER_400,
|
||||||
|
'400-499': Color.BG_CHERRY_500,
|
||||||
|
'500-599': Color.BG_ROBIN_500,
|
||||||
|
Other: Color.BG_SIENNA_500,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const widget = useMemo<Widgets>(
|
||||||
|
() =>
|
||||||
|
getStatusCodeBarChartWidgetData(domainName, endPointName, {
|
||||||
|
items: [...domainListFilters.items, ...filters.items],
|
||||||
|
op: filters.op,
|
||||||
|
}),
|
||||||
|
[domainName, endPointName, domainListFilters, filters],
|
||||||
|
);
|
||||||
|
|
||||||
|
const graphClickHandler = useCallback(
|
||||||
|
(
|
||||||
|
xValue: number,
|
||||||
|
yValue: number,
|
||||||
|
mouseX: number,
|
||||||
|
mouseY: number,
|
||||||
|
metric?: { [key: string]: string },
|
||||||
|
queryData?: { queryName: string; inFocusOrNot: boolean },
|
||||||
|
): void => {
|
||||||
|
const customFilters = getCustomFiltersForBarChart(metric);
|
||||||
|
handleGraphClick({
|
||||||
|
xValue,
|
||||||
|
yValue,
|
||||||
|
mouseX,
|
||||||
|
mouseY,
|
||||||
|
metric,
|
||||||
|
queryData,
|
||||||
|
widget,
|
||||||
|
navigateToExplorerPages,
|
||||||
|
navigateToExplorer,
|
||||||
|
notifications,
|
||||||
|
graphClick,
|
||||||
|
customFilters,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[
|
||||||
|
widget,
|
||||||
|
navigateToExplorerPages,
|
||||||
|
navigateToExplorer,
|
||||||
|
notifications,
|
||||||
|
graphClick,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getUPlotChartOptions({
|
getUPlotChartOptions({
|
||||||
@ -100,6 +185,8 @@ function StatusCodeBarCharts({
|
|||||||
minTimeScale: Math.floor(minTime / 1e9),
|
minTimeScale: Math.floor(minTime / 1e9),
|
||||||
maxTimeScale: Math.floor(maxTime / 1e9),
|
maxTimeScale: Math.floor(maxTime / 1e9),
|
||||||
panelType: PANEL_TYPES.BAR,
|
panelType: PANEL_TYPES.BAR,
|
||||||
|
onClickHandler: graphClickHandler,
|
||||||
|
customSeries: getCustomSeries,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
minTime,
|
minTime,
|
||||||
@ -109,6 +196,8 @@ function StatusCodeBarCharts({
|
|||||||
formattedEndPointStatusCodeBarChartsDataPayload,
|
formattedEndPointStatusCodeBarChartsDataPayload,
|
||||||
formattedEndPointStatusCodeLatencyBarChartsDataPayload,
|
formattedEndPointStatusCodeLatencyBarChartsDataPayload,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
|
graphClickHandler,
|
||||||
|
getCustomSeries,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import '../Explorer.styles.scss';
|
|||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { Spin, Table, Typography } from 'antd';
|
import { Spin, Table, Typography } from 'antd';
|
||||||
import axios from 'api';
|
import axios from 'api';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
@ -130,6 +131,7 @@ function DomainList({
|
|||||||
(item) => item.key === record.key,
|
(item) => item.key === record.key,
|
||||||
);
|
);
|
||||||
setSelectedDomainIndex(dataIndex);
|
setSelectedDomainIndex(dataIndex);
|
||||||
|
logEvent('API Monitoring: Domain name row clicked', {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
className: 'expanded-clickable-row',
|
className: 'expanded-clickable-row',
|
||||||
@ -147,6 +149,7 @@ function DomainList({
|
|||||||
handleClose={(): void => {
|
handleClose={(): void => {
|
||||||
setSelectedDomainIndex(-1);
|
setSelectedDomainIndex(-1);
|
||||||
}}
|
}}
|
||||||
|
domainListFilters={query?.filters}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
@ -3,13 +3,14 @@ import './Explorer.styles.scss';
|
|||||||
import { FilterOutlined } from '@ant-design/icons';
|
import { FilterOutlined } from '@ant-design/icons';
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { Switch, Typography } from 'antd';
|
import { Switch, Typography } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||||
import { useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
@ -21,6 +22,10 @@ function Explorer(): JSX.Element {
|
|||||||
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
const { currentQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
logEvent('API Monitoring: Landing page visited', {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { handleChangeQueryData } = useQueryOperations({
|
const { handleChangeQueryData } = useQueryOperations({
|
||||||
index: 0,
|
index: 0,
|
||||||
query: currentQuery.builder.queryData[0],
|
query: currentQuery.builder.queryData[0],
|
||||||
@ -64,7 +69,12 @@ function Explorer(): JSX.Element {
|
|||||||
style={{ marginLeft: 'auto' }}
|
style={{ marginLeft: 'auto' }}
|
||||||
checked={showIP}
|
checked={showIP}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
setShowIP((showIP) => !showIP);
|
setShowIP((showIP): boolean => {
|
||||||
|
logEvent('API Monitoring: Show IP addresses clicked', {
|
||||||
|
showIP: !showIP,
|
||||||
|
});
|
||||||
|
return !showIP;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,16 +8,23 @@ import {
|
|||||||
} from 'components/QuickFilters/types';
|
} from 'components/QuickFilters/types';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
|
import { GraphClickMetaData } from 'container/GridCardLayout/useNavigateToExplorerPages';
|
||||||
|
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { ArrowUpDown, ChevronDown, ChevronRight } from 'lucide-react';
|
import { ArrowUpDown, ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
|
import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
import {
|
import {
|
||||||
BaseAutocompleteData,
|
BaseAutocompleteData,
|
||||||
DataTypes,
|
DataTypes,
|
||||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
TagFilterItem,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { QueryData } from 'types/api/widgets/getQuery';
|
import { QueryData } from 'types/api/widgets/getQuery';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
@ -128,12 +135,15 @@ export const columnsConfig: ColumnType<APIDomainsRowData>[] = [
|
|||||||
sorter: false,
|
sorter: false,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
className: `column`,
|
className: `column`,
|
||||||
render: (lastUsed: number): string => getLastUsedRelativeTime(lastUsed),
|
render: (lastUsed: number | string): string =>
|
||||||
|
lastUsed === 'n/a' || lastUsed === '-'
|
||||||
|
? '-'
|
||||||
|
: getLastUsedRelativeTime(lastUsed as number),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: (
|
title: (
|
||||||
<div>
|
<div>
|
||||||
Rate <span className="round-metric-tag">/s</span>
|
Rate <span className="round-metric-tag">ops/s</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
dataIndex: 'rate',
|
dataIndex: 'rate',
|
||||||
@ -155,21 +165,26 @@ export const columnsConfig: ColumnType<APIDomainsRowData>[] = [
|
|||||||
sorter: false,
|
sorter: false,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
className: `column`,
|
className: `column`,
|
||||||
render: (errorRate: number): React.ReactNode => (
|
render: (errorRate: number | string): React.ReactNode => {
|
||||||
<Progress
|
if (errorRate === 'n/a' || errorRate === '-') {
|
||||||
status="active"
|
return '-';
|
||||||
percent={Number((errorRate * 100).toFixed(1))}
|
}
|
||||||
strokeLinecap="butt"
|
return (
|
||||||
size="small"
|
<Progress
|
||||||
strokeColor={((): string => {
|
status="active"
|
||||||
const errorRatePercent = Number((errorRate * 100).toFixed(1));
|
percent={Number(((errorRate as number) * 100).toFixed(1))}
|
||||||
if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
|
strokeLinecap="butt"
|
||||||
if (errorRatePercent >= 60) return Color.BG_AMBER_500;
|
size="small"
|
||||||
return Color.BG_FOREST_500;
|
strokeColor={((): string => {
|
||||||
})()}
|
const errorRatePercent = Number(((errorRate as number) * 100).toFixed(1));
|
||||||
className="progress-bar error-rate"
|
if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
|
||||||
/>
|
if (errorRatePercent >= 60) return Color.BG_AMBER_500;
|
||||||
),
|
return Color.BG_FOREST_500;
|
||||||
|
})()}
|
||||||
|
className="progress-bar error-rate"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: (
|
title: (
|
||||||
@ -217,9 +232,9 @@ interface APIMonitoringResponseRow {
|
|||||||
data: {
|
data: {
|
||||||
endpoints: number;
|
endpoints: number;
|
||||||
error_rate: number;
|
error_rate: number;
|
||||||
lastseen: number;
|
lastseen: number | string;
|
||||||
[domainNameKey]: string;
|
[domainNameKey]: string;
|
||||||
p99: number;
|
p99: number | string;
|
||||||
rps: number;
|
rps: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -232,12 +247,12 @@ interface EndPointsResponseRow {
|
|||||||
|
|
||||||
export interface APIDomainsRowData {
|
export interface APIDomainsRowData {
|
||||||
key: string;
|
key: string;
|
||||||
domainName: React.ReactNode;
|
domainName: string;
|
||||||
endpointCount: React.ReactNode;
|
endpointCount: number | string;
|
||||||
rate: React.ReactNode;
|
rate: number | string;
|
||||||
errorRate: React.ReactNode;
|
errorRate: number | string;
|
||||||
latency: React.ReactNode;
|
latency: number | string;
|
||||||
lastUsed: React.ReactNode;
|
lastUsed: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename this to a proper name
|
// Rename this to a proper name
|
||||||
@ -246,12 +261,20 @@ export const formatDataForTable = (
|
|||||||
): APIDomainsRowData[] =>
|
): APIDomainsRowData[] =>
|
||||||
data?.map((domain) => ({
|
data?.map((domain) => ({
|
||||||
key: v4(),
|
key: v4(),
|
||||||
domainName: domain.data[domainNameKey] || '',
|
domainName: domain?.data[domainNameKey] || '-',
|
||||||
endpointCount: domain.data.endpoints,
|
endpointCount: domain?.data?.endpoints || '-',
|
||||||
rate: domain.data.rps,
|
rate: domain.data.rps || '-',
|
||||||
errorRate: domain.data.error_rate,
|
errorRate: domain.data.error_rate || '-',
|
||||||
latency: Math.round(domain.data.p99 / 1000000), // Convert from nanoseconds to milliseconds
|
latency:
|
||||||
lastUsed: new Date(Math.floor(domain.data.lastseen / 1000000)).toISOString(), // Convert from nanoseconds to milliseconds
|
domain.data.p99 === 'n/a'
|
||||||
|
? '-'
|
||||||
|
: Math.round(Number(domain.data.p99) / 1000000), // Convert from nanoseconds to milliseconds
|
||||||
|
lastUsed:
|
||||||
|
domain.data.lastseen === 'n/a'
|
||||||
|
? '-'
|
||||||
|
: new Date(
|
||||||
|
Math.floor(Number(domain.data.lastseen) / 1000000),
|
||||||
|
).toISOString(), // Convert from nanoseconds to milliseconds
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Rename this to a proper name
|
// Rename this to a proper name
|
||||||
@ -468,7 +491,6 @@ export const extractPortAndEndpoint = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add icons in the below column headers
|
|
||||||
export const getEndPointsColumnsConfig = (
|
export const getEndPointsColumnsConfig = (
|
||||||
isGroupedByAttribute: boolean,
|
isGroupedByAttribute: boolean,
|
||||||
expandedRowKeys: React.Key[],
|
expandedRowKeys: React.Key[],
|
||||||
@ -576,7 +598,7 @@ export const formatEndPointsDataForTable = (
|
|||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
key: v4(),
|
key: v4(),
|
||||||
endpointName: (endpoint.data['http.url'] as string) || '',
|
endpointName: (endpoint.data['http.url'] as string) || '-',
|
||||||
port,
|
port,
|
||||||
callCount: endpoint.data.A || '-',
|
callCount: endpoint.data.A || '-',
|
||||||
latency:
|
latency:
|
||||||
@ -593,7 +615,6 @@ export const formatEndPointsDataForTable = (
|
|||||||
|
|
||||||
const groupedByAttributeData = groupBy.map((attribute) => attribute.key);
|
const groupedByAttributeData = groupBy.map((attribute) => attribute.key);
|
||||||
|
|
||||||
// TODO: Use tags to show the concatenated attribute values
|
|
||||||
return data?.map((endpoint) => {
|
return data?.map((endpoint) => {
|
||||||
const newEndpointName = groupedByAttributeData
|
const newEndpointName = groupedByAttributeData
|
||||||
.map((attribute) => endpoint.data[attribute])
|
.map((attribute) => endpoint.data[attribute])
|
||||||
@ -639,7 +660,7 @@ export const createFiltersForSelectedRowData = (
|
|||||||
type: null,
|
type: null,
|
||||||
},
|
},
|
||||||
op: '=',
|
op: '=',
|
||||||
value: groupedByMeta[key],
|
value: groupedByMeta[key] || '',
|
||||||
id: key,
|
id: key,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
@ -649,12 +670,10 @@ export const createFiltersForSelectedRowData = (
|
|||||||
|
|
||||||
// First query payload for endpoint metrics
|
// First query payload for endpoint metrics
|
||||||
// Second query payload for endpoint status code
|
// Second query payload for endpoint status code
|
||||||
// Third query payload for endpoint rate over time graph
|
// Third query payload for endpoint dropdown selection
|
||||||
// Fourth query payload for endpoint latency over time graph
|
// Fourth query payload for endpoint dependant services
|
||||||
// Fifth query payload for endpoint dropdown selection
|
// Fifth query payload for endpoint response status count bar chart
|
||||||
// Sixth query payload for endpoint dependant services
|
// Sixth query payload for endpoint response status code latency bar chart
|
||||||
// Seventh query payload for endpoint response status count bar chart
|
|
||||||
// Eighth query payload for endpoint response status code latency bar chart
|
|
||||||
export const getEndPointDetailsQueryPayload = (
|
export const getEndPointDetailsQueryPayload = (
|
||||||
domainName: string,
|
domainName: string,
|
||||||
endPointName: string,
|
endPointName: string,
|
||||||
@ -1101,205 +1120,6 @@ export const getEndPointDetailsQueryPayload = (
|
|||||||
end,
|
end,
|
||||||
step: 60,
|
step: 60,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
graphType: PANEL_TYPES.TIME_SERIES,
|
|
||||||
query: {
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
aggregateAttribute: {
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
id: '------false',
|
|
||||||
isColumn: false,
|
|
||||||
key: '',
|
|
||||||
type: '',
|
|
||||||
},
|
|
||||||
aggregateOperator: 'rate',
|
|
||||||
dataSource: DataSource.TRACES,
|
|
||||||
disabled: false,
|
|
||||||
expression: 'B',
|
|
||||||
filters: {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: '3c76fe0b',
|
|
||||||
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: '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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
having: [],
|
|
||||||
legend: '',
|
|
||||||
limit: null,
|
|
||||||
orderBy: [],
|
|
||||||
queryName: 'B',
|
|
||||||
reduceTo: 'avg',
|
|
||||||
spaceAggregation: 'sum',
|
|
||||||
stepInterval: 60,
|
|
||||||
timeAggregation: 'rate',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
|
||||||
},
|
|
||||||
clickhouse_sql: [
|
|
||||||
{
|
|
||||||
disabled: false,
|
|
||||||
legend: '',
|
|
||||||
name: 'A',
|
|
||||||
query: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
|
|
||||||
promql: [
|
|
||||||
{
|
|
||||||
disabled: false,
|
|
||||||
legend: '',
|
|
||||||
name: 'A',
|
|
||||||
query: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
},
|
|
||||||
variables: {},
|
|
||||||
formatForWeb: false,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
step: 60,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
graphType: PANEL_TYPES.TIME_SERIES,
|
|
||||||
query: {
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
aggregateAttribute: {
|
|
||||||
dataType: DataTypes.Float64,
|
|
||||||
id: 'duration_nano--float64----true',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
key: 'duration_nano',
|
|
||||||
type: '',
|
|
||||||
},
|
|
||||||
aggregateOperator: 'p99',
|
|
||||||
dataSource: DataSource.TRACES,
|
|
||||||
disabled: false,
|
|
||||||
expression: 'B',
|
|
||||||
filters: {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: '63adb3ff',
|
|
||||||
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: '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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
having: [],
|
|
||||||
legend: '',
|
|
||||||
limit: null,
|
|
||||||
orderBy: [],
|
|
||||||
queryName: 'B',
|
|
||||||
reduceTo: 'avg',
|
|
||||||
spaceAggregation: 'sum',
|
|
||||||
stepInterval: 60,
|
|
||||||
timeAggregation: 'p99',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
|
||||||
},
|
|
||||||
clickhouse_sql: [
|
|
||||||
{
|
|
||||||
disabled: false,
|
|
||||||
legend: '',
|
|
||||||
name: 'A',
|
|
||||||
query: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
|
|
||||||
promql: [
|
|
||||||
{
|
|
||||||
disabled: false,
|
|
||||||
legend: '',
|
|
||||||
name: 'A',
|
|
||||||
query: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
},
|
|
||||||
variables: {},
|
|
||||||
formatForWeb: false,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
step: 60,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
selectedTime: 'GLOBAL_TIME',
|
selectedTime: 'GLOBAL_TIME',
|
||||||
graphType: PANEL_TYPES.TABLE,
|
graphType: PANEL_TYPES.TABLE,
|
||||||
@ -1801,7 +1621,7 @@ interface EndPointMetricsData {
|
|||||||
interface EndPointStatusCodeData {
|
interface EndPointStatusCodeData {
|
||||||
key: string;
|
key: string;
|
||||||
statusCode: string;
|
statusCode: string;
|
||||||
count: number;
|
count: number | string;
|
||||||
p99Latency: number | string;
|
p99Latency: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1824,8 +1644,8 @@ export const getFormattedEndPointStatusCodeData = (
|
|||||||
): EndPointStatusCodeData[] =>
|
): EndPointStatusCodeData[] =>
|
||||||
data?.map((row) => ({
|
data?.map((row) => ({
|
||||||
key: v4(),
|
key: v4(),
|
||||||
statusCode: row.data.response_status_code,
|
statusCode: row.data.response_status_code || '-',
|
||||||
count: row.data.A,
|
count: row.data.A || '-',
|
||||||
p99Latency:
|
p99Latency:
|
||||||
row.data.B === 'n/a' ? '-' : Math.round(Number(row.data.B) / 1000000), // Convert from nanoseconds to milliseconds,
|
row.data.B === 'n/a' ? '-' : Math.round(Number(row.data.B) / 1000000), // Convert from nanoseconds to milliseconds,
|
||||||
}));
|
}));
|
||||||
@ -1857,11 +1677,6 @@ export const endPointStatusCodeColumns: ColumnType<EndPointStatusCodeData>[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const apiWidgetInfo = [
|
|
||||||
{ title: 'Rate over time', yAxisUnit: 'ops/s' },
|
|
||||||
{ title: 'Latency over time', yAxisUnit: 'ns' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const statusCodeWidgetInfo = [
|
export const statusCodeWidgetInfo = [
|
||||||
{ yAxisUnit: 'calls' },
|
{ yAxisUnit: 'calls' },
|
||||||
{ yAxisUnit: 'ns' },
|
{ yAxisUnit: 'ns' },
|
||||||
@ -1885,8 +1700,8 @@ export const getFormattedEndPointDropDownData = (
|
|||||||
): EndPointDropDownData[] =>
|
): EndPointDropDownData[] =>
|
||||||
data?.map((row) => ({
|
data?.map((row) => ({
|
||||||
key: v4(),
|
key: v4(),
|
||||||
label: row.data['http.url'],
|
label: row.data['http.url'] || '-',
|
||||||
value: row.data['http.url'],
|
value: row.data['http.url'] || '-',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface DependentServicesResponseRow {
|
interface DependentServicesResponseRow {
|
||||||
@ -1903,6 +1718,7 @@ interface DependentServicesData {
|
|||||||
percentage: number;
|
percentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discuss once about type safety of this function
|
||||||
export const getFormattedDependentServicesData = (
|
export const getFormattedDependentServicesData = (
|
||||||
data: DependentServicesResponseRow[],
|
data: DependentServicesResponseRow[],
|
||||||
): DependentServicesData[] => {
|
): DependentServicesData[] => {
|
||||||
@ -1983,7 +1799,7 @@ export const groupStatusCodes = (
|
|||||||
|
|
||||||
// Track all timestamps
|
// Track all timestamps
|
||||||
series.values.forEach((value) => {
|
series.values.forEach((value) => {
|
||||||
allTimestamps.add(value[0]);
|
allTimestamps.add(Number(value[0]));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize or update the grouped series
|
// Initialize or update the grouped series
|
||||||
@ -2049,8 +1865,114 @@ export const groupStatusCodes = (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.values(groupedSeries);
|
// Define the order of status code ranges
|
||||||
|
const statusCodeOrder = ['200-299', '300-399', '400-499', '500-599', 'Other'];
|
||||||
|
|
||||||
|
// Return the grouped series in the specified order
|
||||||
|
return statusCodeOrder
|
||||||
|
.filter((code) => groupedSeries[code]) // Only include codes that exist in the data
|
||||||
|
.map((code) => groupedSeries[code]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getStatusCodeBarChartWidgetData = (
|
||||||
|
domainName: string,
|
||||||
|
endPointName: string,
|
||||||
|
filters: IBuilderQuery['filters'],
|
||||||
|
): Widgets => ({
|
||||||
|
query: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: '------false',
|
||||||
|
isColumn: false,
|
||||||
|
key: '',
|
||||||
|
type: '',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'count',
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'A',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'c6724407',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'net.peer.name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'net.peer.name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
...filters.items,
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: [],
|
||||||
|
having: [],
|
||||||
|
legend: '',
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'A',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'rate',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryFormulas: [],
|
||||||
|
},
|
||||||
|
clickhouse_sql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
|
||||||
|
promql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
|
||||||
|
isStacked: false,
|
||||||
|
panelTypes: PANEL_TYPES.BAR,
|
||||||
|
title: '',
|
||||||
|
opacity: '',
|
||||||
|
nullZeroValues: '',
|
||||||
|
timePreferance: 'GLOBAL_TIME',
|
||||||
|
softMin: null,
|
||||||
|
softMax: null,
|
||||||
|
selectedLogFields: null,
|
||||||
|
selectedTracesFields: null,
|
||||||
|
});
|
||||||
interface EndPointStatusCodePayloadData {
|
interface EndPointStatusCodePayloadData {
|
||||||
data: {
|
data: {
|
||||||
result: QueryData[];
|
result: QueryData[];
|
||||||
@ -2085,3 +2007,277 @@ export const END_POINT_DETAILS_QUERY_KEYS_ARRAY = [
|
|||||||
REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_BAR_CHARTS_DATA,
|
REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_BAR_CHARTS_DATA,
|
||||||
REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA,
|
REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const getRateOverTimeWidgetData = (
|
||||||
|
domainName: string,
|
||||||
|
endPointName: string,
|
||||||
|
filters: IBuilderQuery['filters'],
|
||||||
|
): Widgets => {
|
||||||
|
const { endpoint, port } = extractPortAndEndpoint(endPointName);
|
||||||
|
const legend = `${
|
||||||
|
port !== '-' && port !== 'n/a' ? `${port}:` : ''
|
||||||
|
}${endpoint}`;
|
||||||
|
return getWidgetQueryBuilder(
|
||||||
|
getWidgetQuery({
|
||||||
|
title: 'Rate Over Time',
|
||||||
|
description: 'Rate over time.',
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: '------false',
|
||||||
|
isColumn: false,
|
||||||
|
key: '',
|
||||||
|
type: '',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'rate',
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'A',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '3c76fe0b',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'net.peer.name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'net.peer.name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
having: [],
|
||||||
|
legend,
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'A',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'rate',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxisUnit: 'ops/s',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLatencyOverTimeWidgetData = (
|
||||||
|
domainName: string,
|
||||||
|
endPointName: string,
|
||||||
|
filters: IBuilderQuery['filters'],
|
||||||
|
): Widgets => {
|
||||||
|
const { endpoint, port } = extractPortAndEndpoint(endPointName);
|
||||||
|
const legend = `${port}:${endpoint}`;
|
||||||
|
return getWidgetQueryBuilder(
|
||||||
|
getWidgetQuery({
|
||||||
|
title: 'Latency Over Time',
|
||||||
|
description: 'Latency over time.',
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.Float64,
|
||||||
|
id: 'duration_nano--float64----true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'duration_nano',
|
||||||
|
type: '',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'p99',
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'A',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '63adb3ff',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'net.peer.name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'net.peer.name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
having: [],
|
||||||
|
legend,
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'A',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'p99',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxisUnit: 'ns',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to get the start and end status codes from a status code range string
|
||||||
|
* @param value Status code range string (e.g. '200-299') or boolean
|
||||||
|
* @returns Tuple of [startStatusCode, endStatusCode] as strings
|
||||||
|
*/
|
||||||
|
const getStartAndEndStatusCode = (
|
||||||
|
value: string | boolean,
|
||||||
|
): [string, string] => {
|
||||||
|
if (!value) {
|
||||||
|
return ['', ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
case '100-199':
|
||||||
|
return ['100', '199'];
|
||||||
|
case '200-299':
|
||||||
|
return ['200', '299'];
|
||||||
|
case '300-399':
|
||||||
|
return ['300', '399'];
|
||||||
|
case '400-499':
|
||||||
|
return ['400', '499'];
|
||||||
|
case '500-599':
|
||||||
|
return ['500', '599'];
|
||||||
|
default:
|
||||||
|
return ['', ''];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates filter items for bar chart based on group by fields and request data
|
||||||
|
* Used specifically for filtering status code ranges in bar charts
|
||||||
|
* @param groupBy Array of group by fields to create filters for
|
||||||
|
* @param requestData Data from graph click containing values to filter on
|
||||||
|
* @returns Array of TagFilterItems with >= and < operators for status code ranges
|
||||||
|
*/
|
||||||
|
export const createGroupByFiltersForBarChart = (
|
||||||
|
groupBy: BaseAutocompleteData[],
|
||||||
|
requestData: GraphClickMetaData,
|
||||||
|
): TagFilterItem[] =>
|
||||||
|
groupBy
|
||||||
|
.map((gb) => {
|
||||||
|
const value = requestData[gb.key];
|
||||||
|
const [startStatusCode, endStatusCode] = getStartAndEndStatusCode(value);
|
||||||
|
return value
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: v4(),
|
||||||
|
key: gb,
|
||||||
|
op: '>=',
|
||||||
|
value: startStatusCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: v4(),
|
||||||
|
key: gb,
|
||||||
|
op: '<=',
|
||||||
|
value: endStatusCode,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
export const getCustomFiltersForBarChart = (
|
||||||
|
metric:
|
||||||
|
| {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
| undefined,
|
||||||
|
): TagFilterItem[] => {
|
||||||
|
if (!metric?.response_status_code) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const [startStatusCode, endStatusCode] = getStartAndEndStatusCode(
|
||||||
|
metric.response_status_code,
|
||||||
|
);
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: v4(),
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'response_status_code--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'response_status_code',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: '>=',
|
||||||
|
value: startStatusCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: v4(),
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'response_status_code--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'response_status_code',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: '<=',
|
||||||
|
value: endStatusCode,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
@ -49,6 +49,7 @@ function FullView({
|
|||||||
isDependedDataLoaded = false,
|
isDependedDataLoaded = false,
|
||||||
onToggleModelHandler,
|
onToggleModelHandler,
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
|
customOnDragSelect,
|
||||||
setCurrentGraphRef,
|
setCurrentGraphRef,
|
||||||
}: FullViewProps): JSX.Element {
|
}: FullViewProps): JSX.Element {
|
||||||
const { safeNavigate } = useSafeNavigate();
|
const { safeNavigate } = useSafeNavigate();
|
||||||
@ -252,7 +253,7 @@ function FullView({
|
|||||||
onToggleModelHandler={onToggleModelHandler}
|
onToggleModelHandler={onToggleModelHandler}
|
||||||
setGraphVisibility={setGraphsVisibilityStates}
|
setGraphVisibility={setGraphsVisibilityStates}
|
||||||
graphVisibility={graphsVisibilityStates}
|
graphVisibility={graphsVisibilityStates}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={customOnDragSelect ?? onDragSelect}
|
||||||
tableProcessedDataRef={tableProcessedDataRef}
|
tableProcessedDataRef={tableProcessedDataRef}
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler}
|
||||||
|
@ -50,6 +50,7 @@ export interface FullViewProps {
|
|||||||
widget: Widgets;
|
widget: Widgets;
|
||||||
fullViewOptions?: boolean;
|
fullViewOptions?: boolean;
|
||||||
onClickHandler?: OnClickPluginOpts['onClick'];
|
onClickHandler?: OnClickPluginOpts['onClick'];
|
||||||
|
customOnDragSelect?: (start: number, end: number) => void;
|
||||||
name: string;
|
name: string;
|
||||||
tableProcessedDataRef: MutableRefObject<RowData[]>;
|
tableProcessedDataRef: MutableRefObject<RowData[]>;
|
||||||
version?: string;
|
version?: string;
|
||||||
|
@ -50,6 +50,7 @@ function WidgetGraphComponent({
|
|||||||
setRequestData,
|
setRequestData,
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
onDragSelect,
|
onDragSelect,
|
||||||
|
customOnDragSelect,
|
||||||
customTooltipElement,
|
customTooltipElement,
|
||||||
openTracesButton,
|
openTracesButton,
|
||||||
onOpenTraceBtnClick,
|
onOpenTraceBtnClick,
|
||||||
@ -327,6 +328,7 @@ function WidgetGraphComponent({
|
|||||||
onToggleModelHandler={onToggleModelHandler}
|
onToggleModelHandler={onToggleModelHandler}
|
||||||
tableProcessedDataRef={tableProcessedDataRef}
|
tableProcessedDataRef={tableProcessedDataRef}
|
||||||
onClickHandler={onClickHandler ?? graphClickHandler}
|
onClickHandler={onClickHandler ?? graphClickHandler}
|
||||||
|
customOnDragSelect={customOnDragSelect}
|
||||||
setCurrentGraphRef={setCurrentGraphRef}
|
setCurrentGraphRef={setCurrentGraphRef}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -36,6 +36,7 @@ function GridCardGraph({
|
|||||||
version,
|
version,
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
onDragSelect,
|
onDragSelect,
|
||||||
|
customOnDragSelect,
|
||||||
customTooltipElement,
|
customTooltipElement,
|
||||||
dataAvailable,
|
dataAvailable,
|
||||||
getGraphData,
|
getGraphData,
|
||||||
@ -272,6 +273,7 @@ function GridCardGraph({
|
|||||||
setRequestData={setRequestData}
|
setRequestData={setRequestData}
|
||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
|
customOnDragSelect={customOnDragSelect}
|
||||||
customTooltipElement={customTooltipElement}
|
customTooltipElement={customTooltipElement}
|
||||||
openTracesButton={openTracesButton}
|
openTracesButton={openTracesButton}
|
||||||
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
||||||
|
@ -33,6 +33,7 @@ export interface WidgetGraphComponentProps {
|
|||||||
setRequestData?: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
setRequestData?: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||||
onClickHandler?: OnClickPluginOpts['onClick'];
|
onClickHandler?: OnClickPluginOpts['onClick'];
|
||||||
onDragSelect: (start: number, end: number) => void;
|
onDragSelect: (start: number, end: number) => void;
|
||||||
|
customOnDragSelect?: (start: number, end: number) => void;
|
||||||
customTooltipElement?: HTMLDivElement;
|
customTooltipElement?: HTMLDivElement;
|
||||||
openTracesButton?: boolean;
|
openTracesButton?: boolean;
|
||||||
onOpenTraceBtnClick?: (record: RowData) => void;
|
onOpenTraceBtnClick?: (record: RowData) => void;
|
||||||
@ -49,6 +50,7 @@ export interface GridCardGraphProps {
|
|||||||
variables?: Dashboard['data']['variables'];
|
variables?: Dashboard['data']['variables'];
|
||||||
version?: string;
|
version?: string;
|
||||||
onDragSelect: (start: number, end: number) => void;
|
onDragSelect: (start: number, end: number) => void;
|
||||||
|
customOnDragSelect?: (start: number, end: number) => void;
|
||||||
customTooltipElement?: HTMLDivElement;
|
customTooltipElement?: HTMLDivElement;
|
||||||
dataAvailable?: (isDataAvailable: boolean) => void;
|
dataAvailable?: (isDataAvailable: boolean) => void;
|
||||||
getGraphData?: (graphData?: MetricRangePayloadProps['data']) => void;
|
getGraphData?: (graphData?: MetricRangePayloadProps['data']) => void;
|
||||||
|
@ -178,6 +178,7 @@ interface HandleGraphClickParams {
|
|||||||
navigateToExplorer: (props: NavigateToExplorerProps) => void;
|
navigateToExplorer: (props: NavigateToExplorerProps) => void;
|
||||||
notifications: NotificationInstance;
|
notifications: NotificationInstance;
|
||||||
graphClick: (props: GraphClickProps) => void;
|
graphClick: (props: GraphClickProps) => void;
|
||||||
|
customFilters?: TagFilterItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleGraphClick = async ({
|
export const handleGraphClick = async ({
|
||||||
@ -192,6 +193,7 @@ export const handleGraphClick = async ({
|
|||||||
navigateToExplorer,
|
navigateToExplorer,
|
||||||
notifications,
|
notifications,
|
||||||
graphClick,
|
graphClick,
|
||||||
|
customFilters,
|
||||||
}: HandleGraphClickParams): Promise<void> => {
|
}: HandleGraphClickParams): Promise<void> => {
|
||||||
const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {};
|
const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {};
|
||||||
|
|
||||||
@ -221,7 +223,7 @@ export const handleGraphClick = async ({
|
|||||||
}: ${key}`,
|
}: ${key}`,
|
||||||
onClick: (): void =>
|
onClick: (): void =>
|
||||||
navigateToExplorer({
|
navigateToExplorer({
|
||||||
filters: result[key].filters,
|
filters: [...result[key].filters, ...(customFilters || [])],
|
||||||
dataSource: result[key].dataSource as DataSource,
|
dataSource: result[key].dataSource as DataSource,
|
||||||
startTime: xValue,
|
startTime: xValue,
|
||||||
endTime: xValue + (stepInterval ?? 60),
|
endTime: xValue + (stepInterval ?? 60),
|
||||||
|
@ -12,7 +12,7 @@ import { v4 } from 'uuid';
|
|||||||
|
|
||||||
import { extractQueryNamesFromExpression } from './utils';
|
import { extractQueryNamesFromExpression } from './utils';
|
||||||
|
|
||||||
type GraphClickMetaData = {
|
export type GraphClickMetaData = {
|
||||||
[key: string]: string | boolean;
|
[key: string]: string | boolean;
|
||||||
queryName: string;
|
queryName: string;
|
||||||
inFocusOrNot: boolean;
|
inFocusOrNot: boolean;
|
||||||
|
@ -279,6 +279,17 @@ function SideNav(): JSX.Element {
|
|||||||
let updatedUserManagementItems: UserManagementMenuItems[] = [
|
let updatedUserManagementItems: UserManagementMenuItems[] = [
|
||||||
manageLicenseMenuItem,
|
manageLicenseMenuItem,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const isApiMonitoringEnabled = featureFlags?.find(
|
||||||
|
(flag) => flag.name === FeatureKeys.THIRD_PARTY_API,
|
||||||
|
)?.active;
|
||||||
|
|
||||||
|
if (!isApiMonitoringEnabled) {
|
||||||
|
updatedMenuItems = updatedMenuItems.filter(
|
||||||
|
(item) => item.key !== ROUTES.API_MONITORING,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isCloudUserVal || isEECloudUserVal) {
|
if (isCloudUserVal || isEECloudUserVal) {
|
||||||
const isOnboardingEnabled =
|
const isOnboardingEnabled =
|
||||||
featureFlags?.find((feature) => feature.name === FeatureKeys.ONBOARDING)
|
featureFlags?.find((feature) => feature.name === FeatureKeys.ONBOARDING)
|
||||||
|
@ -128,6 +128,7 @@ const menuItems: SidebarItem[] = [
|
|||||||
key: ROUTES.API_MONITORING,
|
key: ROUTES.API_MONITORING,
|
||||||
label: 'API Monitoring',
|
label: 'API Monitoring',
|
||||||
icon: <Binoculars size={16} />,
|
icon: <Binoculars size={16} />,
|
||||||
|
isNew: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: ROUTES.LIST_ALL_ALERT,
|
key: ROUTES.LIST_ALL_ALERT,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user