mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 21:05:54 +08:00
feat: added custom cell rendering in gridcard, added new table view in all endpoints
This commit is contained in:
parent
552b103e8b
commit
d6e4e3c5ed
@ -1,34 +1,12 @@
|
|||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { Select } from 'antd';
|
||||||
import {
|
import { getAllEndpointsWidgetData } from 'container/ApiMonitoring/utils';
|
||||||
Select,
|
import GridCard from 'container/GridCardLayout/GridCard';
|
||||||
Spin,
|
|
||||||
Table,
|
|
||||||
TablePaginationConfig,
|
|
||||||
TableProps,
|
|
||||||
Typography,
|
|
||||||
} from 'antd';
|
|
||||||
import { SorterResult } from 'antd/lib/table/interface';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
|
||||||
import {
|
|
||||||
EndPointsTableRowData,
|
|
||||||
formatEndPointsDataForTable,
|
|
||||||
getEndPointsColumnsConfig,
|
|
||||||
getEndPointsQueryPayload,
|
|
||||||
} from 'container/ApiMonitoring/utils';
|
|
||||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useQueries } from 'react-query';
|
|
||||||
import { SuccessResponse } from 'types/api';
|
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import ErrorState from './components/ErrorState';
|
import { VIEWS } from './constants';
|
||||||
import ExpandedRow from './components/ExpandedRow';
|
|
||||||
import { VIEW_TYPES, VIEWS } from './constants';
|
|
||||||
|
|
||||||
function AllEndPoints({
|
function AllEndPoints({
|
||||||
domainName,
|
domainName,
|
||||||
@ -63,13 +41,6 @@ function AllEndPoints({
|
|||||||
{ value: string; label: string }[]
|
{ value: string; label: string }[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const [orderBy, setOrderBy] = useState<{
|
|
||||||
columnName: string;
|
|
||||||
order: 'asc' | 'desc';
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<React.Key[]>([]);
|
|
||||||
|
|
||||||
const handleGroupByChange = useCallback(
|
const handleGroupByChange = useCallback(
|
||||||
(value: IBuilderQuery['groupBy']) => {
|
(value: IBuilderQuery['groupBy']) => {
|
||||||
const groupBy = [];
|
const groupBy = [];
|
||||||
@ -101,109 +72,11 @@ function AllEndPoints({
|
|||||||
}
|
}
|
||||||
}, [groupByFiltersData]);
|
}, [groupByFiltersData]);
|
||||||
|
|
||||||
const { startTime: minTime, endTime: maxTime } = timeRange;
|
const allEndpointsWidgetData = useMemo(
|
||||||
|
() => getAllEndpointsWidgetData(groupBy, domainName),
|
||||||
const queryPayloads = useMemo(
|
[groupBy, domainName],
|
||||||
() => getEndPointsQueryPayload(groupBy, domainName, minTime, maxTime),
|
|
||||||
[groupBy, domainName, minTime, maxTime],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Since only one query here
|
|
||||||
const endPointsDataQueries = useQueries(
|
|
||||||
queryPayloads.map((payload) => ({
|
|
||||||
queryKey: [
|
|
||||||
REACT_QUERY_KEY.GET_ENDPOINTS_LIST_BY_DOMAIN,
|
|
||||||
payload,
|
|
||||||
ENTITY_VERSION_V4,
|
|
||||||
groupBy,
|
|
||||||
],
|
|
||||||
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
|
||||||
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
|
|
||||||
enabled: !!payload,
|
|
||||||
staleTime: 60 * 1000, // 1 minute stale time : optimize this part
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
const endPointsDataQuery = endPointsDataQueries[0];
|
|
||||||
const {
|
|
||||||
data: allEndPointsData,
|
|
||||||
isLoading,
|
|
||||||
isRefetching,
|
|
||||||
isError,
|
|
||||||
refetch,
|
|
||||||
} = endPointsDataQuery;
|
|
||||||
|
|
||||||
const endPointsColumnsConfig = useMemo(
|
|
||||||
() => getEndPointsColumnsConfig(groupBy.length > 0, expandedRowKeys),
|
|
||||||
[groupBy.length, expandedRowKeys],
|
|
||||||
);
|
|
||||||
|
|
||||||
const expandedRowRender = (record: EndPointsTableRowData): JSX.Element => (
|
|
||||||
<ExpandedRow
|
|
||||||
domainName={domainName}
|
|
||||||
selectedRowData={record}
|
|
||||||
setSelectedEndPointName={setSelectedEndPointName}
|
|
||||||
setSelectedView={setSelectedView}
|
|
||||||
orderBy={orderBy}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleGroupByRowClick = (record: EndPointsTableRowData): void => {
|
|
||||||
if (expandedRowKeys.includes(record.key)) {
|
|
||||||
setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key));
|
|
||||||
} else {
|
|
||||||
setExpandedRowKeys((expandedRowKeys) => [...expandedRowKeys, record.key]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRowClick = (record: EndPointsTableRowData): void => {
|
|
||||||
if (groupBy.length === 0) {
|
|
||||||
setSelectedEndPointName(record.endpointName); // this will open up the endpoint details tab
|
|
||||||
setSelectedView(VIEW_TYPES.ENDPOINT_STATS);
|
|
||||||
logEvent('API Monitoring: Endpoint name row clicked', {});
|
|
||||||
} else {
|
|
||||||
handleGroupByRowClick(record); // this will prepare the nested query payload
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTableChange: TableProps<EndPointsTableRowData>['onChange'] = useCallback(
|
|
||||||
(
|
|
||||||
_pagination: TablePaginationConfig,
|
|
||||||
_filters: Record<string, (string | number | boolean)[] | null>,
|
|
||||||
sorter:
|
|
||||||
| SorterResult<EndPointsTableRowData>
|
|
||||||
| SorterResult<EndPointsTableRowData>[],
|
|
||||||
): void => {
|
|
||||||
if ('field' in sorter && sorter.order) {
|
|
||||||
setOrderBy({
|
|
||||||
columnName: sorter.field as string,
|
|
||||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setOrderBy(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const formattedEndPointsData = useMemo(
|
|
||||||
() =>
|
|
||||||
formatEndPointsDataForTable(
|
|
||||||
allEndPointsData?.payload?.data?.result[0]?.table?.rows,
|
|
||||||
groupBy,
|
|
||||||
orderBy,
|
|
||||||
),
|
|
||||||
[groupBy, allEndPointsData, orderBy],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
return (
|
|
||||||
<div className="all-endpoints-error-state-wrapper">
|
|
||||||
<ErrorState refetch={refetch} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="all-endpoints-container">
|
<div className="all-endpoints-container">
|
||||||
<div className="group-by-container">
|
<div className="group-by-container">
|
||||||
@ -221,47 +94,16 @@ function AllEndPoints({
|
|||||||
/>{' '}
|
/>{' '}
|
||||||
</div>
|
</div>
|
||||||
<div className="endpoints-table-container">
|
<div className="endpoints-table-container">
|
||||||
<div className="endpoints-table-header">Endpoint overview</div>
|
<GridCard
|
||||||
<Table
|
widget={allEndpointsWidgetData}
|
||||||
columns={endPointsColumnsConfig}
|
isQueryEnabled
|
||||||
loading={{
|
onDragSelect={(): void => {}}
|
||||||
spinning: isLoading || isRefetching,
|
customOnDragSelect={(): void => {}}
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
customTimeRange={timeRange}
|
||||||
|
customOnRowClick={(props): void => {
|
||||||
|
setSelectedEndPointName(props['http.url'] as string);
|
||||||
|
setSelectedView(VIEWS.ENDPOINT_STATS);
|
||||||
}}
|
}}
|
||||||
dataSource={isLoading || isRefetching ? [] : formattedEndPointsData}
|
|
||||||
locale={{
|
|
||||||
emptyText:
|
|
||||||
isLoading || isRefetching ? null : (
|
|
||||||
<div className="no-filtered-endpoints-message-container">
|
|
||||||
<div className="no-filtered-endpoints-message-content">
|
|
||||||
<img
|
|
||||||
src="/Icons/emptyState.svg"
|
|
||||||
alt="thinking-emoji"
|
|
||||||
className="empty-state-svg"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Typography.Text className="no-filtered-endpoints-message">
|
|
||||||
This query had no results. Edit your query and try again!
|
|
||||||
</Typography.Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
scroll={{ x: true }}
|
|
||||||
tableLayout="fixed"
|
|
||||||
onRow={(record): { onClick: () => void; className: string } => ({
|
|
||||||
onClick: (): void => handleRowClick(record),
|
|
||||||
className: 'clickable-row',
|
|
||||||
})}
|
|
||||||
expandable={{
|
|
||||||
expandedRowRender: groupBy.length > 0 ? expandedRowRender : undefined,
|
|
||||||
expandedRowKeys,
|
|
||||||
expandIconColumnIndex: -1,
|
|
||||||
}}
|
|
||||||
rowClassName={(_, index): string =>
|
|
||||||
index % 2 === 0 ? 'table-row-dark' : 'table-row-light'
|
|
||||||
}
|
|
||||||
onChange={handleTableChange}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3276,6 +3276,329 @@ export const END_POINT_DETAILS_QUERY_KEYS_ARRAY = [
|
|||||||
REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA,
|
REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const getAllEndpointsWidgetData = (
|
||||||
|
groupBy: BaseAutocompleteData[],
|
||||||
|
domainName: string,
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
|
): Widgets => {
|
||||||
|
const isGroupedByAttribute = groupBy.length > 0;
|
||||||
|
|
||||||
|
const widget = getWidgetQueryBuilder(
|
||||||
|
getWidgetQuery({
|
||||||
|
title: 'Endpoint Overview',
|
||||||
|
description: 'Endpoint Overview',
|
||||||
|
panelTypes: PANEL_TYPES.TABLE,
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'span_id--string----true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'span_id',
|
||||||
|
type: '',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'count',
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'A',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'ec316e57',
|
||||||
|
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: '212678b9',
|
||||||
|
key: {
|
||||||
|
key: 'kind_string',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: '',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'kind_string--string----true',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: 'Client',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: isGroupedByAttribute
|
||||||
|
? [...defaultGroupBy, ...groupBy]
|
||||||
|
: defaultGroupBy,
|
||||||
|
having: [],
|
||||||
|
legend: 'Num of Calls',
|
||||||
|
limit: 1000,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'A',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'count',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: '46d57857',
|
||||||
|
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: '212678b9',
|
||||||
|
key: {
|
||||||
|
key: 'kind_string',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: '',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'kind_string--string----true',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: 'Client',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: isGroupedByAttribute
|
||||||
|
? [...defaultGroupBy, ...groupBy]
|
||||||
|
: defaultGroupBy,
|
||||||
|
having: [],
|
||||||
|
legend: 'Latency (ms)',
|
||||||
|
limit: 1000,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'B',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'p99',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'timestamp------false',
|
||||||
|
isColumn: false,
|
||||||
|
key: 'timestamp',
|
||||||
|
type: '',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'max',
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'C',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '4a237616',
|
||||||
|
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: '212678b9',
|
||||||
|
key: {
|
||||||
|
key: 'kind_string',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: '',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'kind_string--string----true',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: 'Client',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: isGroupedByAttribute
|
||||||
|
? [...defaultGroupBy, ...groupBy]
|
||||||
|
: defaultGroupBy,
|
||||||
|
having: [],
|
||||||
|
legend: 'Last Used',
|
||||||
|
limit: 1000,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'C',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'max',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'span_id--string----true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'span_id',
|
||||||
|
type: '',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'count',
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
disabled: true,
|
||||||
|
expression: 'D',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'f162de1e',
|
||||||
|
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: '3df0ac1d',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.bool,
|
||||||
|
id: 'has_error--bool----true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'has_error',
|
||||||
|
type: '',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '212678b9',
|
||||||
|
key: {
|
||||||
|
key: 'kind_string',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: '',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'kind_string--string----true',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: 'Client',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: isGroupedByAttribute
|
||||||
|
? [...defaultGroupBy, ...groupBy]
|
||||||
|
: defaultGroupBy,
|
||||||
|
having: [],
|
||||||
|
legend: '',
|
||||||
|
limit: 1000,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'D',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'count',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryFormulas: [
|
||||||
|
{
|
||||||
|
queryName: 'F1',
|
||||||
|
expression: '(D/A)*100',
|
||||||
|
disabled: false,
|
||||||
|
legend: 'error percentage',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxisUnit: 'ops/s',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
widget.renderColumnCell = {
|
||||||
|
A: (numOfCalls: any): ReactNode => (
|
||||||
|
<span>
|
||||||
|
{numOfCalls === 'n/a' || numOfCalls === undefined ? '-' : numOfCalls}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
B: (latency: any): ReactNode => (
|
||||||
|
<span>
|
||||||
|
{latency === 'n/a' || latency === undefined
|
||||||
|
? '-'
|
||||||
|
: `${Math.round(Number(latency) / 1000000)} ms`}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
C: (lastUsed: any): ReactNode => (
|
||||||
|
<span>
|
||||||
|
{lastUsed === 'n/a' || lastUsed === undefined
|
||||||
|
? '-'
|
||||||
|
: getLastUsedRelativeTime(
|
||||||
|
new Date(
|
||||||
|
new Date(Math.floor(Number(lastUsed) / 1000000)).toISOString(),
|
||||||
|
).getTime(),
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
F1: (errorRate: any): ReactNode => (
|
||||||
|
<Progress
|
||||||
|
status="active"
|
||||||
|
percent={Number(
|
||||||
|
((errorRate === 'n/a' || errorRate === '-'
|
||||||
|
? 0
|
||||||
|
: errorRate) as number).toFixed(1),
|
||||||
|
)}
|
||||||
|
strokeLinecap="butt"
|
||||||
|
size="small"
|
||||||
|
strokeColor={((): // eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
|
string => {
|
||||||
|
const errorRatePercent = Number(
|
||||||
|
((errorRate === 'n/a' || errorRate === '-'
|
||||||
|
? 0
|
||||||
|
: errorRate) as number).toFixed(1),
|
||||||
|
);
|
||||||
|
if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
|
||||||
|
if (errorRatePercent >= 60) return Color.BG_AMBER_500;
|
||||||
|
return Color.BG_FOREST_500;
|
||||||
|
})()}
|
||||||
|
className="progress-bar error-rate"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return widget;
|
||||||
|
};
|
||||||
|
|
||||||
export const getRateOverTimeWidgetData = (
|
export const getRateOverTimeWidgetData = (
|
||||||
domainName: string,
|
domainName: string,
|
||||||
endPointName: string,
|
endPointName: string,
|
||||||
|
@ -56,6 +56,7 @@ function WidgetGraphComponent({
|
|||||||
onOpenTraceBtnClick,
|
onOpenTraceBtnClick,
|
||||||
customSeries,
|
customSeries,
|
||||||
customErrorMessage,
|
customErrorMessage,
|
||||||
|
customOnRowClick,
|
||||||
}: WidgetGraphComponentProps): JSX.Element {
|
}: WidgetGraphComponentProps): JSX.Element {
|
||||||
const { safeNavigate } = useSafeNavigate();
|
const { safeNavigate } = useSafeNavigate();
|
||||||
const [deleteModal, setDeleteModal] = useState(false);
|
const [deleteModal, setDeleteModal] = useState(false);
|
||||||
@ -380,6 +381,7 @@ function WidgetGraphComponent({
|
|||||||
openTracesButton={openTracesButton}
|
openTracesButton={openTracesButton}
|
||||||
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
||||||
customSeries={customSeries}
|
customSeries={customSeries}
|
||||||
|
customOnRowClick={customOnRowClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -48,6 +48,7 @@ function GridCardGraph({
|
|||||||
end,
|
end,
|
||||||
analyticsEvent,
|
analyticsEvent,
|
||||||
customTimeRange,
|
customTimeRange,
|
||||||
|
customOnRowClick,
|
||||||
}: GridCardGraphProps): JSX.Element {
|
}: GridCardGraphProps): JSX.Element {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
@ -287,6 +288,7 @@ function GridCardGraph({
|
|||||||
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
||||||
customSeries={customSeries}
|
customSeries={customSeries}
|
||||||
customErrorMessage={isInternalServerError ? customErrorMessage : undefined}
|
customErrorMessage={isInternalServerError ? customErrorMessage : undefined}
|
||||||
|
customOnRowClick={customOnRowClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,6 +39,7 @@ export interface WidgetGraphComponentProps {
|
|||||||
onOpenTraceBtnClick?: (record: RowData) => void;
|
onOpenTraceBtnClick?: (record: RowData) => void;
|
||||||
customSeries?: (data: QueryData[]) => uPlot.Series[];
|
customSeries?: (data: QueryData[]) => uPlot.Series[];
|
||||||
customErrorMessage?: string;
|
customErrorMessage?: string;
|
||||||
|
customOnRowClick?: (record: RowData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GridCardGraphProps {
|
export interface GridCardGraphProps {
|
||||||
@ -65,6 +66,7 @@ export interface GridCardGraphProps {
|
|||||||
startTime: number;
|
startTime: number;
|
||||||
endTime: number;
|
endTime: number;
|
||||||
};
|
};
|
||||||
|
customOnRowClick?: (record: RowData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetGraphVisibilityStateOnLegendClickProps {
|
export interface GetGraphVisibilityStateOnLegendClickProps {
|
||||||
|
@ -43,6 +43,7 @@ function GridTableComponent({
|
|||||||
sticky,
|
sticky,
|
||||||
openTracesButton,
|
openTracesButton,
|
||||||
onOpenTraceBtnClick,
|
onOpenTraceBtnClick,
|
||||||
|
customOnRowClick,
|
||||||
widgetId,
|
widgetId,
|
||||||
...props
|
...props
|
||||||
}: GridTableComponentProps): JSX.Element {
|
}: GridTableComponentProps): JSX.Element {
|
||||||
@ -214,6 +215,18 @@ function GridTableComponent({
|
|||||||
[newColumnData],
|
[newColumnData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const newColumnsWithRenderColumnCell = useMemo(
|
||||||
|
() =>
|
||||||
|
newColumnData.map((column) => ({
|
||||||
|
...column,
|
||||||
|
...('dataIndex' in column &&
|
||||||
|
props.renderColumnCell?.[column.dataIndex as string]
|
||||||
|
? { render: props.renderColumnCell[column.dataIndex as string] }
|
||||||
|
: {}),
|
||||||
|
})),
|
||||||
|
[newColumnData, props.renderColumnCell],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eventEmitter.emit(Events.TABLE_COLUMNS_DATA, {
|
eventEmitter.emit(Events.TABLE_COLUMNS_DATA, {
|
||||||
columns: newColumnData,
|
columns: newColumnData,
|
||||||
@ -227,15 +240,22 @@ function GridTableComponent({
|
|||||||
query={query}
|
query={query}
|
||||||
queryTableData={data}
|
queryTableData={data}
|
||||||
loading={false}
|
loading={false}
|
||||||
columns={openTracesButton ? columnDataWithOpenTracesButton : newColumnData}
|
columns={
|
||||||
|
openTracesButton
|
||||||
|
? columnDataWithOpenTracesButton
|
||||||
|
: newColumnsWithRenderColumnCell
|
||||||
|
}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
sticky={sticky}
|
sticky={sticky}
|
||||||
widgetId={widgetId}
|
widgetId={widgetId}
|
||||||
onRow={
|
onRow={
|
||||||
openTracesButton
|
openTracesButton || customOnRowClick
|
||||||
? (record): React.HTMLAttributes<HTMLElement> => ({
|
? (record): React.HTMLAttributes<HTMLElement> => ({
|
||||||
onClick: (): void => {
|
onClick: (): void => {
|
||||||
onOpenTraceBtnClick?.(record);
|
if (openTracesButton) {
|
||||||
|
onOpenTraceBtnClick?.(record);
|
||||||
|
}
|
||||||
|
customOnRowClick?.(record);
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
: undefined
|
: undefined
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
ThresholdOperators,
|
ThresholdOperators,
|
||||||
ThresholdProps,
|
ThresholdProps,
|
||||||
} from 'container/NewWidget/RightContainer/Threshold/types';
|
} from 'container/NewWidget/RightContainer/Threshold/types';
|
||||||
|
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
||||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||||
import { ColumnUnit } from 'types/api/dashboard/getAll';
|
import { ColumnUnit } from 'types/api/dashboard/getAll';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
@ -17,7 +18,9 @@ export type GridTableComponentProps = {
|
|||||||
searchTerm?: string;
|
searchTerm?: string;
|
||||||
openTracesButton?: boolean;
|
openTracesButton?: boolean;
|
||||||
onOpenTraceBtnClick?: (record: RowData) => void;
|
onOpenTraceBtnClick?: (record: RowData) => void;
|
||||||
|
customOnRowClick?: (record: RowData) => void;
|
||||||
widgetId?: string;
|
widgetId?: string;
|
||||||
|
renderColumnCell?: QueryTableProps['renderColumnCell'];
|
||||||
} & Pick<LogsExplorerTableProps, 'data'> &
|
} & Pick<LogsExplorerTableProps, 'data'> &
|
||||||
Omit<TableProps<RowData>, 'columns' | 'dataSource'>;
|
Omit<TableProps<RowData>, 'columns' | 'dataSource'>;
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ function PanelWrapper({
|
|||||||
openTracesButton,
|
openTracesButton,
|
||||||
onOpenTraceBtnClick,
|
onOpenTraceBtnClick,
|
||||||
customSeries,
|
customSeries,
|
||||||
|
customOnRowClick,
|
||||||
}: PanelWrapperProps): JSX.Element {
|
}: PanelWrapperProps): JSX.Element {
|
||||||
const Component = PanelTypeVsPanelWrapper[
|
const Component = PanelTypeVsPanelWrapper[
|
||||||
selectedGraph || widget.panelTypes
|
selectedGraph || widget.panelTypes
|
||||||
@ -46,6 +47,7 @@ function PanelWrapper({
|
|||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
openTracesButton={openTracesButton}
|
openTracesButton={openTracesButton}
|
||||||
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
||||||
|
customOnRowClick={customOnRowClick}
|
||||||
customSeries={customSeries}
|
customSeries={customSeries}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ function TablePanelWrapper({
|
|||||||
searchTerm,
|
searchTerm,
|
||||||
openTracesButton,
|
openTracesButton,
|
||||||
onOpenTraceBtnClick,
|
onOpenTraceBtnClick,
|
||||||
|
customOnRowClick,
|
||||||
}: PanelWrapperProps): JSX.Element {
|
}: PanelWrapperProps): JSX.Element {
|
||||||
const panelData =
|
const panelData =
|
||||||
(queryResponse.data?.payload?.data?.result?.[0] as any)?.table || [];
|
(queryResponse.data?.payload?.data?.result?.[0] as any)?.table || [];
|
||||||
@ -26,7 +27,9 @@ function TablePanelWrapper({
|
|||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
openTracesButton={openTracesButton}
|
openTracesButton={openTracesButton}
|
||||||
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
||||||
|
customOnRowClick={customOnRowClick}
|
||||||
widgetId={widget.id}
|
widgetId={widget.id}
|
||||||
|
renderColumnCell={widget.renderColumnCell}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...GRID_TABLE_CONFIG}
|
{...GRID_TABLE_CONFIG}
|
||||||
/>
|
/>
|
||||||
|
@ -28,6 +28,7 @@ export type PanelWrapperProps = {
|
|||||||
customTooltipElement?: HTMLDivElement;
|
customTooltipElement?: HTMLDivElement;
|
||||||
openTracesButton?: boolean;
|
openTracesButton?: boolean;
|
||||||
onOpenTraceBtnClick?: (record: RowData) => void;
|
onOpenTraceBtnClick?: (record: RowData) => void;
|
||||||
|
customOnRowClick?: (record: RowData) => void;
|
||||||
customSeries?: (data: QueryData[]) => uPlot.Series[];
|
customSeries?: (data: QueryData[]) => uPlot.Series[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
|||||||
import { GetWidgetQueryBuilderProps } from 'container/MetricsApplication/types';
|
import { GetWidgetQueryBuilderProps } from 'container/MetricsApplication/types';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import {
|
||||||
|
IBuilderFormula,
|
||||||
|
IBuilderQuery,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
@ -12,6 +15,7 @@ interface GetWidgetQueryProps {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
queryData: IBuilderQuery[];
|
queryData: IBuilderQuery[];
|
||||||
|
queryFormulas?: IBuilderFormula[];
|
||||||
panelTypes?: PANEL_TYPES;
|
panelTypes?: PANEL_TYPES;
|
||||||
yAxisUnit?: string;
|
yAxisUnit?: string;
|
||||||
columnUnits?: Record<string, string>;
|
columnUnits?: Record<string, string>;
|
||||||
@ -67,7 +71,7 @@ export function getWidgetQuery(
|
|||||||
promql: [],
|
promql: [],
|
||||||
builder: {
|
builder: {
|
||||||
queryData: props.queryData,
|
queryData: props.queryData,
|
||||||
queryFormulas: [],
|
queryFormulas: (props.queryFormulas as IBuilderFormula[]) || [],
|
||||||
},
|
},
|
||||||
clickhouse_sql: [],
|
clickhouse_sql: [],
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||||
|
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Layout } from 'react-grid-layout';
|
import { Layout } from 'react-grid-layout';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
@ -113,6 +114,7 @@ export interface IBaseWidget {
|
|||||||
}
|
}
|
||||||
export interface Widgets extends IBaseWidget {
|
export interface Widgets extends IBaseWidget {
|
||||||
query: Query;
|
query: Query;
|
||||||
|
renderColumnCell?: QueryTableProps['renderColumnCell'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PromQLWidgets extends IBaseWidget {
|
export interface PromQLWidgets extends IBaseWidget {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user