diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts
index f96778df2b..09d75871a3 100644
--- a/frontend/src/constants/reactQueryKeys.ts
+++ b/frontend/src/constants/reactQueryKeys.ts
@@ -55,6 +55,7 @@ export const REACT_QUERY_KEY = {
// API Monitoring Query Keys
GET_DOMAINS_LIST: 'GET_DOMAINS_LIST',
GET_ENDPOINTS_LIST_BY_DOMAIN: 'GET_ENDPOINTS_LIST_BY_DOMAIN',
+ GET_TOP_ERRORS_BY_DOMAIN: 'GET_TOP_ERRORS_BY_DOMAIN',
GET_NESTED_ENDPOINTS_LIST: 'GET_NESTED_ENDPOINTS_LIST',
GET_ENDPOINT_METRICS_DATA: 'GET_ENDPOINT_METRICS_DATA',
GET_ENDPOINT_STATUS_CODE_DATA: 'GET_ENDPOINT_STATUS_CODE_DATA',
diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx
index 0521299a00..93100d92a7 100644
--- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx
+++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/DomainDetails.tsx
@@ -21,6 +21,7 @@ import AllEndPoints from './AllEndPoints';
import DomainMetrics from './components/DomainMetrics';
import { VIEW_TYPES, VIEWS } from './constants';
import EndPointDetailsWrapper from './EndPointDetailsWrapper';
+import TopErrors from './TopErrors';
const TimeRangeOffset = 1000000000;
@@ -181,6 +182,14 @@ function DomainDetails({
>
Endpoint Details
+
+ Top Errors
+
{selectedView === VIEW_TYPES.ALL_ENDPOINTS && (
@@ -203,6 +212,13 @@ function DomainDetails({
timeRange={modalTimeRange}
/>
)}
+
+ {selectedView === VIEW_TYPES.TOP_ERRORS && (
+
+ )}
>
)}
diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx
new file mode 100644
index 0000000000..484bafc144
--- /dev/null
+++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx
@@ -0,0 +1,120 @@
+import { LoadingOutlined } from '@ant-design/icons';
+import { Spin, Table, Typography } from 'antd';
+import { DEFAULT_ENTITY_VERSION } from 'constants/app';
+import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
+import {
+ formatTopErrorsDataForTable,
+ getTopErrorsColumnsConfig,
+ getTopErrorsQueryPayload,
+ TopErrorsResponseRow,
+} 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 './components/ErrorState';
+
+function TopErrors({
+ domainName,
+ timeRange,
+}: {
+ domainName: string;
+ timeRange: {
+ startTime: number;
+ endTime: number;
+ };
+}): JSX.Element {
+ const { startTime: minTime, endTime: maxTime } = timeRange;
+
+ const queryPayloads = useMemo(
+ () => getTopErrorsQueryPayload(domainName, minTime, maxTime),
+ [domainName, minTime, maxTime],
+ );
+
+ // Since only one query here
+ const topErrorsDataQueries = useQueries(
+ queryPayloads.map((payload) => ({
+ queryKey: [
+ REACT_QUERY_KEY.GET_TOP_ERRORS_BY_DOMAIN,
+ payload,
+ DEFAULT_ENTITY_VERSION,
+ ],
+ queryFn: (): Promise> =>
+ GetMetricQueryRange(payload, DEFAULT_ENTITY_VERSION),
+ enabled: !!payload,
+ staleTime: 60 * 1000, // 1 minute stale time : optimize this part
+ })),
+ );
+
+ const topErrorsDataQuery = topErrorsDataQueries[0];
+ const {
+ data: topErrorsData,
+ isLoading,
+ isRefetching,
+ isError,
+ refetch,
+ } = topErrorsDataQuery;
+
+ const topErrorsColumnsConfig = useMemo(() => getTopErrorsColumnsConfig(), []);
+
+ const formattedTopErrorsData = useMemo(
+ () =>
+ formatTopErrorsDataForTable(
+ topErrorsData?.payload?.data?.result as TopErrorsResponseRow[],
+ ),
+ [topErrorsData],
+ );
+
+ if (isError) {
+ return (
+
+
+
+ );
+ }
+
+ console.log('uncaught topErrors Data', formattedTopErrorsData);
+
+ return (
+
+
+
Top Errors
+
} />,
+ }}
+ dataSource={isLoading || isRefetching ? [] : formattedTopErrorsData}
+ locale={{
+ emptyText:
+ isLoading || isRefetching ? null : (
+
+
+

+
+
+ This query had no results. Edit your query and try again!
+
+
+
+ ),
+ }}
+ scroll={{ x: true }}
+ tableLayout="fixed"
+ rowClassName={(_, index): string =>
+ index % 2 === 0 ? 'table-row-dark' : 'table-row-light'
+ }
+ />
+
+
+ );
+}
+
+export default TopErrors;
diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/constants.ts b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/constants.ts
index 9b3314d855..1fbaf85bdc 100644
--- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/constants.ts
+++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/constants.ts
@@ -1,9 +1,11 @@
export enum VIEWS {
ALL_ENDPOINTS = 'all_endpoints',
ENDPOINT_DETAILS = 'endpoint_details',
+ TOP_ERRORS = 'top_errors',
}
export const VIEW_TYPES = {
ALL_ENDPOINTS: VIEWS.ALL_ENDPOINTS,
ENDPOINT_DETAILS: VIEWS.ENDPOINT_DETAILS,
+ TOP_ERRORS: VIEWS.TOP_ERRORS,
};
diff --git a/frontend/src/container/ApiMonitoring/utils.tsx b/frontend/src/container/ApiMonitoring/utils.tsx
index f44fd5a939..c9cb2035e3 100644
--- a/frontend/src/container/ApiMonitoring/utils.tsx
+++ b/frontend/src/container/ApiMonitoring/utils.tsx
@@ -633,6 +633,158 @@ export const getEndPointsQueryPayload = (
];
};
+export const getTopErrorsQueryPayload = (
+ 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: {
+ id: '------false',
+ dataType: DataTypes.String,
+ key: '',
+ isColumn: false,
+ type: '',
+ isJSON: false,
+ },
+ timeAggregation: 'rate',
+ spaceAggregation: 'sum',
+ functions: [],
+ filters: {
+ op: 'AND',
+ items: [
+ {
+ id: '04da97bd',
+ key: {
+ key: 'kind_string',
+ dataType: DataTypes.String,
+ type: '',
+ isColumn: true,
+ isJSON: false,
+ id: 'kind_string--string----true',
+ },
+ op: '=',
+ value: 'Client',
+ },
+ {
+ id: '75d65388',
+ key: {
+ key: 'status_message',
+ dataType: DataTypes.String,
+ type: '',
+ isColumn: true,
+ isJSON: false,
+ id: 'status_message--string----true',
+ },
+ op: 'exists',
+ value: '',
+ },
+ {
+ id: 'b1af6bdb',
+ key: {
+ key: 'http.url',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: false,
+ isJSON: false,
+ id: 'http.url--string--tag--false',
+ },
+ op: 'exists',
+ value: '',
+ },
+ {
+ id: '4872bf91',
+ key: {
+ key: 'net.peer.name',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: false,
+ isJSON: false,
+ id: 'net.peer.name--string--tag--false',
+ },
+ op: '=',
+ value: domainName,
+ },
+ ],
+ },
+ expression: 'A',
+ disabled: false,
+ stepInterval: 60,
+ having: [],
+ limit: 10,
+ orderBy: [
+ {
+ columnName: 'timestamp',
+ order: 'desc',
+ },
+ ],
+ groupBy: [
+ {
+ key: 'http.url',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: false,
+ isJSON: false,
+ id: 'http.url--string--tag--false',
+ },
+ {
+ key: 'status_code',
+ dataType: DataTypes.Float64,
+ type: '',
+ isColumn: true,
+ isJSON: false,
+ id: 'status_code--float64----true',
+ },
+ {
+ key: 'status_message',
+ dataType: DataTypes.String,
+ type: '',
+ isColumn: true,
+ isJSON: false,
+ id: 'status_message--string----true',
+ },
+ ],
+ legend: '',
+ reduceTo: 'avg',
+ },
+ ],
+ queryFormulas: [],
+ },
+ clickhouse_sql: [
+ {
+ disabled: false,
+ legend: '',
+ name: 'A',
+ query: '',
+ },
+ ],
+ id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
+ promql: [
+ {
+ disabled: false,
+ legend: '',
+ name: 'A',
+ query: '',
+ },
+ ],
+ queryType: EQueryType.QUERY_BUILDER,
+ },
+ variables: {},
+ start,
+ end,
+ step: 240,
+ },
+];
+
export interface EndPointsTableRowData {
key: string;
endpointName: string;
@@ -911,6 +1063,98 @@ export const formatEndPointsDataForTable = (
return formattedData;
};
+export interface TopErrorsResponseRow {
+ metric: {
+ 'http.url': string;
+ status_code: string;
+ status_message: string;
+ };
+ values: [number, string][];
+ queryName: string;
+ legend: string;
+}
+
+export interface TopErrorsTableRowData {
+ key: string;
+ endpointName: string;
+ statusCode: string;
+ statusMessage: string;
+ count: number | string;
+}
+
+export const formatTopErrorsDataForTable = (
+ data: TopErrorsResponseRow[] | undefined,
+): TopErrorsTableRowData[] => {
+ if (!data) return [];
+
+ return data.map((row) => ({
+ key: v4(),
+ endpointName:
+ row.metric['http.url'] === 'n/a' || row.metric['http.url'] === undefined
+ ? '-'
+ : row.metric['http.url'],
+ statusCode:
+ row.metric.status_code === 'n/a' || row.metric.status_code === undefined
+ ? '-'
+ : row.metric.status_code,
+ statusMessage:
+ row.metric.status_message === 'n/a' ||
+ row.metric.status_message === undefined
+ ? '-'
+ : row.metric.status_message,
+ count:
+ row.values &&
+ row.values[0] &&
+ row.values[0][1] !== undefined &&
+ row.values[0][1] !== 'n/a'
+ ? row.values[0][1]
+ : '-',
+ }));
+};
+
+export const getTopErrorsColumnsConfig = (): ColumnType[] => [
+ {
+ title: Endpoint
,
+ dataIndex: 'endpointName',
+ key: 'endpointName',
+ width: 180,
+ ellipsis: true,
+ sorter: false,
+ className: 'column',
+ render: (text: string, record: TopErrorsTableRowData): React.ReactNode => (
+ {record.endpointName}
+ ),
+ },
+ {
+ title: Status code
,
+ dataIndex: 'statusCode',
+ key: 'statusCode',
+ width: 180,
+ ellipsis: true,
+ sorter: false,
+ align: 'right',
+ className: `column`,
+ },
+ {
+ title: Status message
,
+ dataIndex: 'statusMessage',
+ key: 'statusMessage',
+ width: 180,
+ ellipsis: true,
+ align: 'right',
+ className: `column`,
+ },
+ {
+ title: Count
,
+ dataIndex: 'count',
+ key: 'count',
+ width: 120,
+ sorter: false,
+ align: 'right',
+ className: `column`,
+ },
+];
+
export const createFiltersForSelectedRowData = (
selectedRowData: EndPointsTableRowData,
currentFilters?: IBuilderQuery['filters'],