feat: new dep. services table added

This commit is contained in:
sawhil 2025-04-22 17:34:47 +05:30 committed by Sahil Khan
parent bd071e3e60
commit 1118c56356
3 changed files with 522 additions and 86 deletions

View File

@ -690,12 +690,6 @@
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
.top-services-title {
border-bottom: 1px solid var(--bg-slate-500);
padding: 10px 12px;
border-radius: 3px 3px 0px 0px;
background: rgba(171, 189, 255, 0.04);
.title-wrapper {
display: inline-flex;
padding: 1px 2px;
@ -711,9 +705,125 @@
line-height: 18px;
letter-spacing: -0.07px;
}
}
.dependent-services-container {
padding: 10px 12px;
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
.ant-table {
.ant-table-thead > tr > th {
padding: 12px;
font-weight: 500;
font-size: 12px;
line-height: 18px;
border-bottom: none;
color: var(--text-vanilla-400);
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 18px;
/* 163.636% */
letter-spacing: 0.44px;
text-transform: uppercase;
background: none;
&::before {
background-color: transparent;
}
}
.ant-table-thead > tr > th:has(.status-code-header) {
background: var(--bg-ink-300);
opacity: 0.6;
}
.ant-table-cell {
padding: 12px;
font-size: 13px;
line-height: 20px;
color: var(--bg-vanilla-100);
border-bottom: none;
background: var(--bg-ink-400);
}
.ant-table-cell:has(.col-title) {
background: rgba(171, 189, 255, 0.04);
}
.ant-table-cell:has(.top-services-item-latency) {
text-align: center;
opacity: 0.8;
background: rgba(171, 189, 255, 0.04);
}
.ant-table-cell:has(.top-services-item-latency-title) {
text-align: center;
opacity: 0.8;
background: rgba(171, 189, 255, 0.04);
}
.ant-table-tbody > tr:hover > td {
background: rgba(255, 255, 255, 0.04);
}
.ant-table-cell:first-child {
text-align: justify;
}
.ant-table-cell:nth-child(2) {
padding-left: 16px;
padding-right: 16px;
}
.ant-table-cell:nth-child(n + 3) {
padding-right: 24px;
}
.ant-table-tbody > tr > td {
border-bottom: none;
}
.ant-table-thead
> tr
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
background-color: transparent;
}
.ant-empty-normal {
visibility: hidden;
}
.table-row-dark {
background: var(--bg-ink-300);
}
.ant-table-content {
margin-bottom: 0px;
}
}
.no-status-code-data-message-container {
height: 30vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.no-status-code-data-message-content {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: fit-content;
padding: 24px;
}
.no-status-code-data-message {
margin-top: 8px;
}
}
.top-services-item {
display: flex;
justify-content: space-between;
@ -743,6 +853,7 @@
.top-services-item-progress-bar {
background-color: var(--bg-slate-400);
border-radius: 2px;
height: 100%;
position: absolute;
top: 0;
@ -758,7 +869,7 @@
.top-services-load-more {
border-top: 1px solid var(--bg-slate-500);
padding-top: 10px;
padding: 10px;
color: var(--text-vanilla-400);
font-family: Inter;

View File

@ -1,6 +1,12 @@
import { Typography } from 'antd';
import '../DomainDetails.styles.scss';
import { Table, TablePaginationConfig, Typography } from 'antd';
import Skeleton from 'antd/lib/skeleton';
import { getFormattedDependentServicesData } from 'container/ApiMonitoring/utils';
import {
dependentServicesColumns,
DependentServicesData,
getFormattedDependentServicesData,
} from 'container/ApiMonitoring/utils';
import { UnfoldVertical } from 'lucide-react';
import { useMemo, useState } from 'react';
import { UseQueryResult } from 'react-query';
@ -23,19 +29,25 @@ function DependentServices({
isRefetching,
} = dependentServicesQuery;
const [currentRenderCount, setCurrentRenderCount] = useState(0);
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const dependentServicesData = useMemo(() => {
const formattedDependentServicesData = getFormattedDependentServicesData(
data?.payload?.data?.result[0].table.rows,
const handleShowMoreClick = (): void => {
setIsExpanded((prev) => !prev);
};
const dependentServicesData = useMemo(
(): DependentServicesData[] =>
getFormattedDependentServicesData(data?.payload?.data?.result[0].table.rows),
[data],
);
setCurrentRenderCount(Math.min(formattedDependentServicesData.length, 5));
return formattedDependentServicesData;
}, [data]);
const renderItems = useMemo(
() => dependentServicesData.slice(0, currentRenderCount),
[currentRenderCount, dependentServicesData],
const paginationConfig = useMemo(
(): TablePaginationConfig => ({
pageSize: isExpanded ? dependentServicesData.length : 5,
hideOnSinglePage: true,
position: ['none', 'none'],
}),
[isExpanded, dependentServicesData.length],
);
if (isLoading || isRefetching) {
@ -48,56 +60,47 @@ function DependentServices({
return (
<div className="top-services-content">
<div className="top-services-title">
<span className="title-wrapper">Dependent Services</span>
</div>
<div className="dependent-services-container">
{renderItems.length === 0 ? (
<div className="no-dependent-services-message-container">
<div className="no-dependent-services-message-content">
<Table
loading={isLoading || isRefetching}
dataSource={dependentServicesData || []}
columns={dependentServicesColumns}
rowClassName="table-row-dark"
pagination={paginationConfig}
locale={{
emptyText:
isLoading || isRefetching ? null : (
<div className="no-status-code-data-message-container">
<div className="no-status-code-data-message-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-dependent-services-message">
<Typography.Text className="no-status-code-data-message">
This query had no results. Edit your query and try again!
</Typography.Text>
</div>
</div>
) : (
renderItems.map((item) => (
<div className="top-services-item" key={item.key}>
<div className="top-services-item-progress">
<div className="top-services-item-key">{item.serviceName}</div>
<div className="top-services-item-count">{item.count}</div>
<div
className="top-services-item-progress-bar"
style={{ width: `${item.percentage}%` }}
),
}}
/>
</div>
<div className="top-services-item-percentage">
{item.percentage.toFixed(2)}%
</div>
</div>
))
)}
{currentRenderCount < dependentServicesData.length && (
{dependentServicesData.length > 5 && (
<div
className="top-services-load-more"
onClick={(): void => setCurrentRenderCount(dependentServicesData.length)}
onClick={handleShowMoreClick}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
setCurrentRenderCount(dependentServicesData.length);
handleShowMoreClick();
}
}}
role="button"
tabIndex={0}
>
<UnfoldVertical size={14} />
Show more...
{isExpanded ? 'Show less...' : 'Show more...'}
</div>
)}
</div>

View File

@ -15,6 +15,7 @@ import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { cloneDeep } from 'lodash-es';
import { ArrowUpDown, ChevronDown, ChevronRight } from 'lucide-react';
import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil';
import { ReactNode } from 'react';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import {
@ -1472,14 +1473,14 @@ export const getEndPointDetailsQueryPayload = (
key: 'span_id',
type: '',
},
aggregateOperator: 'count',
aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: 'a32988a4',
id: 'bdac4904',
key: {
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
@ -1492,7 +1493,7 @@ export const getEndPointDetailsQueryPayload = (
value: endPointName,
},
{
id: '5a15032f',
id: 'b78ff216',
key: {
dataType: DataTypes.String,
id: 'net.peer.name--string--tag--false',
@ -1527,10 +1528,227 @@ export const getEndPointDetailsQueryPayload = (
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
timeAggregation: 'count_distinct',
},
{
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: '74f9d185',
key: {
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
op: '=',
value: endPointName,
},
{
id: 'a9024472',
key: {
dataType: DataTypes.String,
id: 'net.peer.name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'net.peer.name',
type: 'tag',
},
op: '=',
value: domainName,
},
...filters.items,
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'service.name--string--resource--true',
isColumn: true,
isJSON: false,
key: 'service.name',
type: 'resource',
},
],
having: [],
legend: 'p99 latency',
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'p99',
},
{
aggregateAttribute: {
dataType: DataTypes.String,
id: '------false',
isColumn: false,
key: '',
type: '',
},
aggregateOperator: 'rate',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'C',
filters: {
items: [
{
id: 'b7e36a72',
key: {
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
op: '=',
value: endPointName,
},
{
id: '1b6c062d',
key: {
dataType: DataTypes.String,
id: 'net.peer.name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'net.peer.name',
type: 'tag',
},
op: '=',
value: domainName,
},
...filters.items,
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'service.name--string--resource--true',
isColumn: true,
isJSON: false,
key: 'service.name',
type: 'resource',
},
],
having: [],
legend: 'request per second',
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
},
{
aggregateAttribute: {
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
isJSON: false,
key: 'span_id',
type: '',
},
aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES,
disabled: true,
expression: 'D',
filters: {
items: [
{
id: 'ede7cbfe',
key: {
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
op: '=',
value: endPointName,
},
{
id: 'd14792a8',
key: {
dataType: DataTypes.String,
id: 'net.peer.name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'net.peer.name',
type: 'tag',
},
op: '=',
value: domainName,
},
{
id: '3212bf1a',
key: {
dataType: DataTypes.bool,
id: 'has_error--bool----true',
isColumn: true,
isJSON: false,
key: 'has_error',
type: '',
},
op: '=',
value: 'true',
},
...filters.items,
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'service.name--string--resource--true',
isColumn: true,
isJSON: false,
key: 'service.name',
type: 'resource',
},
],
having: [],
legend: 'count',
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',
},
],
queryFormulas: [
{
queryName: 'F1',
expression: '(D/A)*100',
disabled: false,
legend: 'error percentage',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
@ -1963,31 +2181,135 @@ export const getFormattedEndPointDropDownData = (
interface DependentServicesResponseRow {
data: {
['service.name']: string;
A: number;
A: number | string;
B: number | string;
C: number | string;
F1: number | string;
};
}
interface DependentServicesData {
key: string;
export interface ServiceData {
serviceName: string;
count: number;
percentage: number;
count: number | string;
percentage: number | string;
}
export interface DependentServicesData {
key: string;
serviceData: ServiceData;
latency: number | string;
rate: number | string;
errorPercentage: number | string;
}
// Discuss once about type safety of this function
export const getFormattedDependentServicesData = (
data: DependentServicesResponseRow[],
// eslint-disable-next-line sonarjs/cognitive-complexity
): DependentServicesData[] => {
if (!data) return [];
const totalCount = data?.reduce((acc, row) => acc + row.data.A, 0);
const totalCount = data?.reduce((acc, row) => acc + Number(row.data.A), 0);
return data?.map((row) => ({
key: v4(),
serviceName: row.data['service.name'],
count: row.data.A,
percentage: Number(((row.data.A / totalCount) * 100).toFixed(2)),
serviceData: {
serviceName: row.data['service.name'] || '-',
count:
row.data.A !== undefined && row.data.A !== '-' && row.data.A !== 'n/a'
? Number(row.data.A)
: '-',
percentage:
totalCount > 0 &&
row.data.A !== undefined &&
row.data.A !== '-' &&
row.data.A !== 'n/a'
? Number(((Number(row.data.A) / totalCount) * 100).toFixed(2))
: 0,
},
latency:
row.data.B !== undefined && row.data.B !== '-' && row.data.B !== 'n/a'
? Math.round(Number(row.data.B) / 1000000)
: '-',
rate:
row.data.C !== undefined && row.data.C !== '-' && row.data.C !== 'n/a'
? row.data.C
: '-',
errorPercentage:
row.data.F1 !== undefined && row.data.F1 !== '-' && row.data.F1 !== 'n/a'
? row.data.F1
: 0,
}));
};
export const dependentServicesColumns: ColumnType<DependentServicesData>[] = [
{
title: <span className="title-wrapper col-title">Dependent Services</span>,
dataIndex: 'serviceData',
key: 'serviceData',
render: (serviceData: ServiceData): ReactNode => (
<div className="top-services-item">
<div className="top-services-item-progress">
<div className="top-services-item-key">{serviceData.serviceName}</div>
<div className="top-services-item-count">{serviceData.count}</div>
<div
className="top-services-item-progress-bar"
style={{ width: `${serviceData.percentage}%` }}
/>
</div>
<div className="top-services-item-percentage">
{typeof serviceData.percentage === 'number'
? serviceData.percentage.toFixed(2)
: serviceData.percentage}
%
</div>
</div>
),
sorter: (a: DependentServicesData, b: DependentServicesData): number =>
Number(a.serviceData.count) - Number(b.serviceData.count),
},
{
title: (
<span className="top-services-item-latency-title col-title">
AVG. LATENCY
</span>
),
dataIndex: 'latency',
key: 'latency',
render: (latency: number): ReactNode => (
<div className="top-services-item-latency">{latency || '-'}ms</div>
),
sorter: (a: DependentServicesData, b: DependentServicesData): number =>
Number(a.latency) - Number(b.latency),
},
{
title: (
<span className="top-services-item-error-percentage-title col-title">
ERROR %
</span>
),
dataIndex: 'errorPercentage',
key: 'errorPercentage',
align: 'center',
render: (errorPercentage: number): ReactNode => (
<div className="top-services-item-error-percentage">{errorPercentage}%</div>
),
sorter: (a: DependentServicesData, b: DependentServicesData): number =>
Number(a.errorPercentage) - Number(b.errorPercentage),
},
{
title: (
<span className="top-services-item-rate-title col-title">AVG. RATE</span>
),
dataIndex: 'rate',
key: 'rate',
align: 'right',
render: (rate: number): ReactNode => (
<div className="top-services-item-rate">{rate || '-'} ops/sec</div>
),
sorter: (a: DependentServicesData, b: DependentServicesData): number =>
Number(a.rate) - Number(b.rate),
},
];
export const getFormattedChartData = (
data: MetricRangePayloadProps,
newLegendArray: string[],