Compare commits
6 Commits
main
...
feat/expor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f59e223570 | ||
|
|
c922121612 | ||
|
|
e7e4590911 | ||
|
|
3e512f8847 | ||
|
|
999583dda6 | ||
|
|
03856f47d6 |
@@ -1,8 +1,8 @@
|
||||
import './Download.styles.scss';
|
||||
|
||||
import { CloudDownloadOutlined } from '@ant-design/icons';
|
||||
import { Button, Dropdown, MenuProps } from 'antd';
|
||||
import { Button, Dropdown, MenuProps, Tooltip } from 'antd';
|
||||
import { Excel } from 'antd-table-saveas-excel';
|
||||
import { DownloadIcon } from 'lucide-react';
|
||||
import { unparse } from 'papaparse';
|
||||
|
||||
import { DownloadProps } from './Download.types';
|
||||
@@ -56,17 +56,18 @@ function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown menu={menu} trigger={['click']}>
|
||||
<Button
|
||||
className="download-button"
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
type="link"
|
||||
>
|
||||
<CloudDownloadOutlined />
|
||||
Download
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Tooltip title="Download" placement="top">
|
||||
<Dropdown menu={menu} trigger={['click']}>
|
||||
<Button
|
||||
className="download-button"
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
type="link"
|
||||
>
|
||||
<DownloadIcon size={20} />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ function LogsExplorerTable({
|
||||
loading={isLoading}
|
||||
rootClassName="logs-table"
|
||||
sticky
|
||||
downloadOption={{
|
||||
isDownloadEnabled: true,
|
||||
fileName: 'logs-table-export',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import './QueryTable.styles.scss';
|
||||
|
||||
import type { TablePaginationConfig } from 'antd/es/table';
|
||||
import cx from 'classnames';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import Download from 'container/Download/Download';
|
||||
import { IServiceName } from 'container/MetricsApplication/Tabs/types';
|
||||
import { DEFAULT_PER_PAGE_OPTIONS } from 'hooks/queryPagination';
|
||||
import { getDefaultPaginationConfig } from 'hooks/queryPagination/utils';
|
||||
import {
|
||||
createTableColumnsFromQuery,
|
||||
RowData,
|
||||
@@ -14,7 +17,10 @@ import { useParams } from 'react-router-dom';
|
||||
|
||||
import useTableContextMenu from './Drilldown/useTableContextMenu';
|
||||
import { QueryTableProps } from './QueryTable.intefaces';
|
||||
import { createDownloadableData } from './utils';
|
||||
import { createDownloadableData, getFormattedTimestamp } from './utils';
|
||||
|
||||
// I saw this done in other places
|
||||
const PER_PAGE_OPTIONS: number[] = [10, ...DEFAULT_PER_PAGE_OPTIONS];
|
||||
|
||||
export function QueryTable({
|
||||
queryTableData,
|
||||
@@ -130,10 +136,20 @@ export function QueryTable({
|
||||
[tableColumns, isQueryTypeBuilder, enableDrillDown, handleColumnClick],
|
||||
);
|
||||
|
||||
const [pageSize, setPageSize] = useState(
|
||||
getDefaultPaginationConfig(PER_PAGE_OPTIONS).limit,
|
||||
);
|
||||
const paginationConfig = {
|
||||
pageSize: 10,
|
||||
showSizeChanger: false,
|
||||
hideOnSinglePage: true,
|
||||
pageSize,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: PER_PAGE_OPTIONS,
|
||||
hideOnSinglePage: false,
|
||||
position: ['topRight'] as TablePaginationConfig['position'],
|
||||
onChange: (_page: number, newPageSize: number): void => {
|
||||
if (newPageSize !== pageSize) {
|
||||
setPageSize(newPageSize);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const [filterTable, setFilterTable] = useState<RowData[] | null>(null);
|
||||
@@ -159,16 +175,16 @@ export function QueryTable({
|
||||
|
||||
return (
|
||||
<>
|
||||
{isDownloadEnabled && (
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Download
|
||||
data={downloadableData}
|
||||
fileName={`${fileName}-${servicename}-${getFormattedTimestamp()}`}
|
||||
isLoading={loading as boolean}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="query-table">
|
||||
{isDownloadEnabled && (
|
||||
<div className="query-table--download">
|
||||
<Download
|
||||
data={downloadableData}
|
||||
fileName={`${fileName}-${servicename}`}
|
||||
isLoading={loading as boolean}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<ResizeTable
|
||||
columns={columnsWithClickHandlers}
|
||||
tableLayout="fixed"
|
||||
|
||||
@@ -1,14 +1,55 @@
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
|
||||
/**
|
||||
* Strips Ant table key and converts all values to String for CSV/Excel export.
|
||||
*/
|
||||
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'),
|
||||
}));
|
||||
if (!inputData || inputData.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get all keys from the first row since it's a table
|
||||
const allKeys = new Set<string>();
|
||||
Object.keys(inputData[0]).forEach((key) => {
|
||||
// Exclude internal keys used by Ant table
|
||||
if (key !== 'key') {
|
||||
allKeys.add(key);
|
||||
}
|
||||
});
|
||||
|
||||
return inputData.map((row) => {
|
||||
const downloadableRow: Record<string, string> = {};
|
||||
|
||||
allKeys.forEach((key) => {
|
||||
const value = row[key];
|
||||
|
||||
// TODO : Possible change to format and normalize headers
|
||||
const formattedKey = key;
|
||||
|
||||
// Handle null and undefined
|
||||
if (value === null || value === undefined) {
|
||||
downloadableRow[formattedKey] = '';
|
||||
|
||||
// Handle objects/arrays by stringifying
|
||||
} else if (typeof value === 'object') {
|
||||
downloadableRow[formattedKey] = JSON.stringify(value);
|
||||
|
||||
// Else make sure it's a string
|
||||
} else {
|
||||
downloadableRow[formattedKey] = String(value);
|
||||
}
|
||||
});
|
||||
|
||||
return downloadableRow;
|
||||
});
|
||||
}
|
||||
|
||||
export function getFormattedTimestamp(): string {
|
||||
const now = new Date();
|
||||
const pad = (n: number): string => n.toString().padStart(2, '0');
|
||||
return `${now.getFullYear()}_${pad(now.getMonth() + 1)}_${pad(
|
||||
now.getDate(),
|
||||
)}_${pad(now.getHours())}_${pad(now.getMinutes())}_${pad(now.getSeconds())}`;
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ function TableView({
|
||||
queryTableData={queryTableData as QueryDataV3[]}
|
||||
loading={isLoading}
|
||||
sticky
|
||||
downloadOption={{
|
||||
isDownloadEnabled: true,
|
||||
fileName: 'traces-table-export',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Space.Compact>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const DEFAULT_PER_PAGE_OPTIONS: number[] = [25, 50, 100, 200];
|
||||
export const DEFAULT_PER_PAGE_OPTIONS: number[] = [25, 50, 100, 200, 500, 1000];
|
||||
|
||||
Reference in New Issue
Block a user