mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 11:55:52 +08:00
[Feat]: Download as CSV and Execl and Search option for key-operation table (#3848)
* refactor: done with the download option for key-operation * refactor: added search option for key operation metrics * refactor: done with the download option for key-operation * refactor: added search option for key operation metrics * refactor: updated downloadable data * refactor: updated downloadable data for metrics key operation * refactor: updated the data * refactor: map with the correct value for export * refactor: updated downloabable data for metrics * refactor: updated the data for metrics * refactor: added safety check --------- Co-authored-by: Ankit Nayan <ankit@signoz.io>
This commit is contained in:
parent
f94a5f4481
commit
64f0ff05f9
4
frontend/src/container/Download/Download.styles.scss
Normal file
4
frontend/src/container/Download/Download.styles.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.download-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
77
frontend/src/container/Download/Download.tsx
Normal file
77
frontend/src/container/Download/Download.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import './Download.styles.scss';
|
||||||
|
|
||||||
|
import { CloudDownloadOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Dropdown, MenuProps } from 'antd';
|
||||||
|
import { Excel } from 'antd-table-saveas-excel';
|
||||||
|
import { unparse } from 'papaparse';
|
||||||
|
|
||||||
|
import { DownloadProps } from './Download.types';
|
||||||
|
|
||||||
|
function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
|
||||||
|
const downloadExcelFile = (): void => {
|
||||||
|
const headers = Object.keys(Object.assign({}, ...data)).map((item) => {
|
||||||
|
const updatedTitle = item
|
||||||
|
.split('_')
|
||||||
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(' ');
|
||||||
|
return {
|
||||||
|
title: updatedTitle,
|
||||||
|
dataIndex: item,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const excel = new Excel();
|
||||||
|
excel
|
||||||
|
.addSheet(fileName)
|
||||||
|
.addColumns(headers)
|
||||||
|
.addDataSource(data, {
|
||||||
|
str2Percent: true,
|
||||||
|
})
|
||||||
|
.saveAs(`${fileName}.xlsx`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadCsvFile = (): void => {
|
||||||
|
const csv = unparse(data);
|
||||||
|
const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
const csvUrl = URL.createObjectURL(csvBlob);
|
||||||
|
const downloadLink = document.createElement('a');
|
||||||
|
downloadLink.href = csvUrl;
|
||||||
|
downloadLink.download = `${fileName}.csv`;
|
||||||
|
downloadLink.click();
|
||||||
|
downloadLink.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
const menu: MenuProps = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
key: 'download-as-excel',
|
||||||
|
label: 'Excel',
|
||||||
|
onClick: downloadExcelFile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'download-as-csv',
|
||||||
|
label: 'CSV',
|
||||||
|
onClick: downloadCsvFile,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown menu={menu} trigger={['click']}>
|
||||||
|
<Button
|
||||||
|
className="download-button"
|
||||||
|
loading={isLoading}
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
>
|
||||||
|
<CloudDownloadOutlined />
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Download.defaultProps = {
|
||||||
|
isLoading: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Download;
|
10
frontend/src/container/Download/Download.types.ts
Normal file
10
frontend/src/container/Download/Download.types.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export type DownloadOptions = {
|
||||||
|
isDownloadEnabled: boolean;
|
||||||
|
fileName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DownloadProps = {
|
||||||
|
data: Record<string, string>[];
|
||||||
|
isLoading?: boolean;
|
||||||
|
fileName: string;
|
||||||
|
};
|
@ -1,15 +1,14 @@
|
|||||||
import { CloudDownloadOutlined, FastBackwardOutlined } from '@ant-design/icons';
|
import { FastBackwardOutlined } from '@ant-design/icons';
|
||||||
import { Button, Divider, Dropdown, MenuProps } from 'antd';
|
import { Button, Divider } from 'antd';
|
||||||
import { Excel } from 'antd-table-saveas-excel';
|
|
||||||
import Controls from 'container/Controls';
|
import Controls from 'container/Controls';
|
||||||
|
import Download from 'container/Download/Download';
|
||||||
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
||||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Pagination } from 'hooks/queryPagination';
|
import { Pagination } from 'hooks/queryPagination';
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
import { OrderPreferenceItems } from 'pages/Logs/config';
|
import { OrderPreferenceItems } from 'pages/Logs/config';
|
||||||
import { unparse } from 'papaparse';
|
import { memo, useMemo } from 'react';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -23,7 +22,7 @@ import {
|
|||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
import { Container, DownloadLogButton } from './styles';
|
import { Container } from './styles';
|
||||||
|
|
||||||
function LogControls(): JSX.Element | null {
|
function LogControls(): JSX.Element | null {
|
||||||
const {
|
const {
|
||||||
@ -97,58 +96,6 @@ function LogControls(): JSX.Element | null {
|
|||||||
[logs],
|
[logs],
|
||||||
);
|
);
|
||||||
|
|
||||||
const downloadExcelFile = useCallback((): void => {
|
|
||||||
const headers = Object.keys(Object.assign({}, ...flattenLogData)).map(
|
|
||||||
(item) => {
|
|
||||||
const updatedTitle = item
|
|
||||||
.split('_')
|
|
||||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
||||||
.join(' ');
|
|
||||||
return {
|
|
||||||
title: updatedTitle,
|
|
||||||
dataIndex: item,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const excel = new Excel();
|
|
||||||
excel
|
|
||||||
.addSheet('log_data')
|
|
||||||
.addColumns(headers)
|
|
||||||
.addDataSource(flattenLogData, {
|
|
||||||
str2Percent: true,
|
|
||||||
})
|
|
||||||
.saveAs('log_data.xlsx');
|
|
||||||
}, [flattenLogData]);
|
|
||||||
|
|
||||||
const downloadCsvFile = useCallback((): void => {
|
|
||||||
const csv = unparse(flattenLogData);
|
|
||||||
const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
||||||
const csvUrl = URL.createObjectURL(csvBlob);
|
|
||||||
const downloadLink = document.createElement('a');
|
|
||||||
downloadLink.href = csvUrl;
|
|
||||||
downloadLink.download = 'log_data.csv';
|
|
||||||
downloadLink.click();
|
|
||||||
downloadLink.remove();
|
|
||||||
}, [flattenLogData]);
|
|
||||||
|
|
||||||
const menu: MenuProps = useMemo(
|
|
||||||
() => ({
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
key: 'download-as-excel',
|
|
||||||
label: 'Excel',
|
|
||||||
onClick: downloadExcelFile,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'download-as-csv',
|
|
||||||
label: 'CSV',
|
|
||||||
onClick: downloadCsvFile,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
[downloadCsvFile, downloadExcelFile],
|
|
||||||
);
|
|
||||||
|
|
||||||
const isLoading = isLogsLoading || isLoadingAggregate;
|
const isLoading = isLogsLoading || isLoadingAggregate;
|
||||||
|
|
||||||
if (liveTail !== 'STOPPED') {
|
if (liveTail !== 'STOPPED') {
|
||||||
@ -157,12 +104,7 @@ function LogControls(): JSX.Element | null {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Dropdown menu={menu} trigger={['click']}>
|
<Download data={flattenLogData} isLoading={isLoading} fileName="log_data" />
|
||||||
<DownloadLogButton loading={isLoading} size="small" type="link">
|
|
||||||
<CloudDownloadOutlined />
|
|
||||||
Download
|
|
||||||
</DownloadLogButton>
|
|
||||||
</Dropdown>
|
|
||||||
<Button
|
<Button
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import getTopOperations from 'api/metrics/getTopOperations';
|
import getTopOperations from 'api/metrics/getTopOperations';
|
||||||
import Spinner from 'components/Spinner';
|
|
||||||
import TopOperationsTable from 'container/MetricsApplication/TopOperationsTable';
|
import TopOperationsTable from 'container/MetricsApplication/TopOperationsTable';
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||||
@ -36,12 +35,7 @@ function TopOperation(): JSX.Element {
|
|||||||
|
|
||||||
const topOperationData = data || [];
|
const topOperationData = data || [];
|
||||||
|
|
||||||
return (
|
return <TopOperationsTable data={topOperationData} isLoading={isLoading} />;
|
||||||
<>
|
|
||||||
{isLoading && <Spinner size="large" tip="Loading..." height="40vh" />}
|
|
||||||
{!isLoading && <TopOperationsTable data={topOperationData} />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TopOperation;
|
export default TopOperation;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { topOperationMetricsDownloadOptions } from 'container/MetricsApplication/constant';
|
||||||
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||||
import { topOperationQueries } from 'container/MetricsApplication/MetricsPageQueries/TopOperationQueries';
|
import { topOperationQueries } from 'container/MetricsApplication/MetricsPageQueries/TopOperationQueries';
|
||||||
import { QueryTable } from 'container/QueryTable';
|
import { QueryTable } from 'container/QueryTable';
|
||||||
@ -109,6 +110,7 @@ function TopOperationMetrics(): JSX.Element {
|
|||||||
queryTableData={queryTableData}
|
queryTableData={queryTableData}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
renderColumnCell={renderColumnCell}
|
renderColumnCell={renderColumnCell}
|
||||||
|
downloadOption={topOperationMetricsDownloadOptions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
.top-operation {
|
||||||
|
position: relative;
|
||||||
|
.top-operation--download {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,32 @@
|
|||||||
import { Tooltip, Typography } from 'antd';
|
import './TopOperationsTable.styles.scss';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import { InputRef, Tooltip, Typography } from 'antd';
|
||||||
|
import { ColumnsType, ColumnType } from 'antd/lib/table';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
|
import Download from 'container/Download/Download';
|
||||||
|
import { filterDropdown } from 'container/ServiceApplication/Filter/FilterDropdown';
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||||
|
import { useRef } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
import { getErrorRate, navigateToTrace } from './utils';
|
import { IServiceName } from './Tabs/types';
|
||||||
|
import {
|
||||||
|
convertedTracesToDownloadData,
|
||||||
|
getErrorRate,
|
||||||
|
navigateToTrace,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
function TopOperationsTable({ data }: TopOperationsTableProps): JSX.Element {
|
function TopOperationsTable({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
}: TopOperationsTableProps): JSX.Element {
|
||||||
|
const searchInput = useRef<InputRef>(null);
|
||||||
|
const { servicename } = useParams<IServiceName>();
|
||||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
@ -34,12 +50,19 @@ function TopOperationsTable({ data }: TopOperationsTableProps): JSX.Element {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns: ColumnsType<TopOperationList> = [
|
const getSearchOption = (): ColumnType<TopOperationList> => ({
|
||||||
{
|
filterDropdown,
|
||||||
title: 'Name',
|
filterIcon: <SearchOutlined />,
|
||||||
dataIndex: 'name',
|
onFilter: (value, record): boolean =>
|
||||||
key: 'name',
|
record.name
|
||||||
width: 100,
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.includes((value as string).toLowerCase()),
|
||||||
|
onFilterDropdownOpenChange: (visible): void => {
|
||||||
|
if (visible) {
|
||||||
|
setTimeout(() => searchInput.current?.select(), 100);
|
||||||
|
}
|
||||||
|
},
|
||||||
render: (text: string): JSX.Element => (
|
render: (text: string): JSX.Element => (
|
||||||
<Tooltip placement="topLeft" title={text}>
|
<Tooltip placement="topLeft" title={text}>
|
||||||
<Typography.Link onClick={(): void => handleOnClick(text)}>
|
<Typography.Link onClick={(): void => handleOnClick(text)}>
|
||||||
@ -47,6 +70,15 @@ function TopOperationsTable({ data }: TopOperationsTableProps): JSX.Element {
|
|||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
),
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns: ColumnsType<TopOperationList> = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: 100,
|
||||||
|
...getSearchOption(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'P50 (in ms)',
|
title: 'P50 (in ms)',
|
||||||
@ -92,15 +124,27 @@ function TopOperationsTable({ data }: TopOperationsTableProps): JSX.Element {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const downloadableData = convertedTracesToDownloadData(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="top-operation">
|
||||||
|
<div className="top-operation--download">
|
||||||
|
<Download
|
||||||
|
data={downloadableData}
|
||||||
|
isLoading={isLoading}
|
||||||
|
fileName={`top-operations-${servicename}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<ResizeTable
|
<ResizeTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
loading={isLoading}
|
||||||
showHeader
|
showHeader
|
||||||
title={(): string => 'Key Operations'}
|
title={(): string => 'Key Operations'}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
rowKey="name"
|
rowKey="name"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +159,7 @@ export interface TopOperationList {
|
|||||||
|
|
||||||
interface TopOperationsTableProps {
|
interface TopOperationsTableProps {
|
||||||
data: TopOperationList[];
|
data: TopOperationList[];
|
||||||
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TopOperationsTable;
|
export default TopOperationsTable;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { DownloadOptions } from 'container/Download/Download.types';
|
||||||
|
|
||||||
export const legend = {
|
export const legend = {
|
||||||
address: '{{address}}',
|
address: '{{address}}',
|
||||||
};
|
};
|
||||||
@ -67,3 +69,8 @@ export enum WidgetKeys {
|
|||||||
SignozExternalCallLatencySum = 'signoz_external_call_latency_sum',
|
SignozExternalCallLatencySum = 'signoz_external_call_latency_sum',
|
||||||
Signoz_latency_bucket = 'signoz_latency_bucket',
|
Signoz_latency_bucket = 'signoz_latency_bucket',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const topOperationMetricsDownloadOptions: DownloadOptions = {
|
||||||
|
isDownloadEnabled: true,
|
||||||
|
fileName: 'top-operation',
|
||||||
|
} as const;
|
||||||
|
@ -35,3 +35,19 @@ export const getNearestHighestBucketValue = (
|
|||||||
|
|
||||||
export const convertMilSecToNanoSec = (value: number): number =>
|
export const convertMilSecToNanoSec = (value: number): number =>
|
||||||
value * 1000000000;
|
value * 1000000000;
|
||||||
|
|
||||||
|
export const convertedTracesToDownloadData = (
|
||||||
|
originalData: TopOperationList[],
|
||||||
|
): Record<string, string>[] =>
|
||||||
|
originalData.map((item) => {
|
||||||
|
const newObj: Record<string, string> = {
|
||||||
|
Name: item.name,
|
||||||
|
'P50 (in ms)': (item.p50 / 1000000).toFixed(2),
|
||||||
|
'P95 (in ms)': (item.p95 / 1000000).toFixed(2),
|
||||||
|
'P99 (in ms)': (item.p99 / 1000000).toFixed(2),
|
||||||
|
'Number of calls': item.numCalls.toString(),
|
||||||
|
'Error Rate (%)': getErrorRate(item).toFixed(2),
|
||||||
|
};
|
||||||
|
|
||||||
|
return newObj;
|
||||||
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { TableProps } from 'antd';
|
import { TableProps } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
|
import { DownloadOptions } from 'container/Download/Download.types';
|
||||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
@ -14,4 +15,5 @@ export type QueryTableProps = Omit<
|
|||||||
renderActionCell?: (record: RowData) => ReactNode;
|
renderActionCell?: (record: RowData) => ReactNode;
|
||||||
modifyColumns?: (columns: ColumnsType<RowData>) => ColumnsType<RowData>;
|
modifyColumns?: (columns: ColumnsType<RowData>) => ColumnsType<RowData>;
|
||||||
renderColumnCell?: Record<string, (record: RowData) => ReactNode>;
|
renderColumnCell?: Record<string, (record: RowData) => ReactNode>;
|
||||||
|
downloadOption?: DownloadOptions;
|
||||||
};
|
};
|
||||||
|
9
frontend/src/container/QueryTable/QueryTable.styles.scss
Normal file
9
frontend/src/container/QueryTable/QueryTable.styles.scss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.query-table {
|
||||||
|
position: relative;
|
||||||
|
.query-table--download {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,14 @@
|
|||||||
|
import './QueryTable.styles.scss';
|
||||||
|
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
|
import Download from 'container/Download/Download';
|
||||||
|
import { IServiceName } from 'container/MetricsApplication/Tabs/types';
|
||||||
import { createTableColumnsFromQuery } from 'lib/query/createTableColumnsFromQuery';
|
import { createTableColumnsFromQuery } from 'lib/query/createTableColumnsFromQuery';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { QueryTableProps } from './QueryTable.intefaces';
|
import { QueryTableProps } from './QueryTable.intefaces';
|
||||||
|
import { createDownloadableData } from './utils';
|
||||||
|
|
||||||
export function QueryTable({
|
export function QueryTable({
|
||||||
queryTableData,
|
queryTableData,
|
||||||
@ -10,8 +16,12 @@ export function QueryTable({
|
|||||||
renderActionCell,
|
renderActionCell,
|
||||||
modifyColumns,
|
modifyColumns,
|
||||||
renderColumnCell,
|
renderColumnCell,
|
||||||
|
downloadOption,
|
||||||
...props
|
...props
|
||||||
}: QueryTableProps): JSX.Element {
|
}: QueryTableProps): JSX.Element {
|
||||||
|
const { isDownloadEnabled = false, fileName = '' } = downloadOption || {};
|
||||||
|
const { servicename } = useParams<IServiceName>();
|
||||||
|
const { loading } = props;
|
||||||
const { columns, dataSource } = useMemo(
|
const { columns, dataSource } = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createTableColumnsFromQuery({
|
createTableColumnsFromQuery({
|
||||||
@ -23,9 +33,21 @@ export function QueryTable({
|
|||||||
[query, queryTableData, renderActionCell, renderColumnCell],
|
[query, queryTableData, renderActionCell, renderColumnCell],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const downloadableData = createDownloadableData(dataSource);
|
||||||
|
|
||||||
const tableColumns = modifyColumns ? modifyColumns(columns) : columns;
|
const tableColumns = modifyColumns ? modifyColumns(columns) : columns;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="query-table">
|
||||||
|
{isDownloadEnabled && (
|
||||||
|
<div className="query-table--download">
|
||||||
|
<Download
|
||||||
|
data={downloadableData}
|
||||||
|
fileName={`${fileName}-${servicename}`}
|
||||||
|
isLoading={loading as boolean}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<ResizeTable
|
<ResizeTable
|
||||||
columns={tableColumns}
|
columns={tableColumns}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
@ -34,5 +56,6 @@ export function QueryTable({
|
|||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
14
frontend/src/container/QueryTable/utils.ts
Normal file
14
frontend/src/container/QueryTable/utils.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||||
|
|
||||||
|
export function createDownloadableData(
|
||||||
|
inputData: RowData[],
|
||||||
|
): Record<string, string>[] {
|
||||||
|
return inputData.map((row) => ({
|
||||||
|
Name: String(row.operation || ''),
|
||||||
|
'P50 (in ns)': String(row.A || ''),
|
||||||
|
'P90 (in ns)': String(row.B || ''),
|
||||||
|
'P99 (in ns)': String(row.C || ''),
|
||||||
|
'Number Of Calls': String(row.F || ''),
|
||||||
|
'Error Rate (%)': String(row.F1 && row.F1 !== 'N/A' ? row.F1 : '0'),
|
||||||
|
}));
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user