mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 01:05:57 +08:00
feat: added download as csv support for table panel type (#5067)
* feat: added download as csv support for table panel type * feat: update the position of download * fix: build issues * fix: address review comments
This commit is contained in:
parent
52e4c2d8ff
commit
76b1e40cbc
@ -42,6 +42,7 @@ function FullView({
|
||||
fullViewOptions = true,
|
||||
version,
|
||||
originalName,
|
||||
tableProcessedDataRef,
|
||||
isDependedDataLoaded = false,
|
||||
onToggleModelHandler,
|
||||
}: FullViewProps): JSX.Element {
|
||||
@ -222,6 +223,7 @@ function FullView({
|
||||
setGraphVisibility={setGraphsVisibilityStates}
|
||||
graphVisibility={graphsVisibilityStates}
|
||||
onDragSelect={onDragSelect}
|
||||
tableProcessedDataRef={tableProcessedDataRef}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@ import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { UplotProps } from 'components/Uplot/Uplot';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
@ -50,6 +51,7 @@ export interface FullViewProps {
|
||||
fullViewOptions?: boolean;
|
||||
onClickHandler?: OnClickPluginOpts['onClick'];
|
||||
name: string;
|
||||
tableProcessedDataRef: MutableRefObject<RowData[]>;
|
||||
version?: string;
|
||||
originalName: string;
|
||||
yAxisUnit?: string;
|
||||
|
@ -12,6 +12,7 @@ import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import history from 'lib/history';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import {
|
||||
Dispatch,
|
||||
@ -33,7 +34,6 @@ import FullView from './FullView';
|
||||
import { Modal } from './styles';
|
||||
import { WidgetGraphComponentProps } from './types';
|
||||
import { getLocalStorageGraphVisibilityState } from './utils';
|
||||
// import { getLocalStorageGraphVisibilityState } from './utils';
|
||||
|
||||
function WidgetGraphComponent({
|
||||
widget,
|
||||
@ -72,6 +72,8 @@ function WidgetGraphComponent({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const tableProcessedDataRef = useRef<RowData[]>([]);
|
||||
|
||||
const { setLayouts, selectedDashboard, setSelectedDashboard } = useDashboard();
|
||||
|
||||
const featureResponse = useSelector<AppState, AppReducer['featureResponse']>(
|
||||
@ -284,6 +286,7 @@ function WidgetGraphComponent({
|
||||
widget={widget}
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
onToggleModelHandler={onToggleModelHandler}
|
||||
tableProcessedDataRef={tableProcessedDataRef}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@ -301,6 +304,7 @@ function WidgetGraphComponent({
|
||||
headerMenuList={headerMenuList}
|
||||
isWarning={isWarning}
|
||||
isFetchingResponse={isFetchingResponse}
|
||||
tableProcessedDataRef={tableProcessedDataRef}
|
||||
/>
|
||||
</div>
|
||||
{queryResponse.isLoading && widget.panelTypes !== PANEL_TYPES.LIST && (
|
||||
@ -319,6 +323,7 @@ function WidgetGraphComponent({
|
||||
graphVisibility={graphVisibility}
|
||||
onClickHandler={onClickHandler}
|
||||
onDragSelect={onDragSelect}
|
||||
tableProcessedDataRef={tableProcessedDataRef}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -4,6 +4,7 @@ export enum MenuItemKeys {
|
||||
Delete = 'delete',
|
||||
Clone = 'clone',
|
||||
CreateAlerts = 'createAlerts',
|
||||
Download = 'download',
|
||||
}
|
||||
|
||||
export const MENUITEM_KEYS_VS_LABELS = {
|
||||
@ -12,4 +13,5 @@ export const MENUITEM_KEYS_VS_LABELS = {
|
||||
[MenuItemKeys.Delete]: 'Delete',
|
||||
[MenuItemKeys.Clone]: 'Clone',
|
||||
[MenuItemKeys.CreateAlerts]: 'Create Alerts',
|
||||
[MenuItemKeys.Download]: 'Download as CSV',
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import './WidgetHeader.styles.scss';
|
||||
|
||||
import {
|
||||
AlertOutlined,
|
||||
CloudDownloadOutlined,
|
||||
CopyOutlined,
|
||||
DeleteOutlined,
|
||||
EditFilled,
|
||||
@ -17,6 +18,9 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import history from 'lib/history';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { unparse } from 'papaparse';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
@ -46,6 +50,7 @@ interface IWidgetHeaderProps {
|
||||
headerMenuList?: MenuItemKeys[];
|
||||
isWarning: boolean;
|
||||
isFetchingResponse: boolean;
|
||||
tableProcessedDataRef: React.MutableRefObject<RowData[]>;
|
||||
}
|
||||
|
||||
function WidgetHeader({
|
||||
@ -61,6 +66,7 @@ function WidgetHeader({
|
||||
headerMenuList,
|
||||
isWarning,
|
||||
isFetchingResponse,
|
||||
tableProcessedDataRef,
|
||||
}: IWidgetHeaderProps): JSX.Element | null {
|
||||
const onEditHandler = useCallback((): void => {
|
||||
const widgetId = widget.id;
|
||||
@ -75,6 +81,17 @@ function WidgetHeader({
|
||||
|
||||
const onCreateAlertsHandler = useCreateAlerts(widget);
|
||||
|
||||
const onDownloadHandler = useCallback((): void => {
|
||||
const csv = unparse(tableProcessedDataRef.current);
|
||||
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 = `${!isEmpty(title) ? title : 'table-panel'}.csv`;
|
||||
downloadLink.click();
|
||||
downloadLink.remove();
|
||||
}, [tableProcessedDataRef, title]);
|
||||
|
||||
const keyMethodMapping = useMemo(
|
||||
() => ({
|
||||
[MenuItemKeys.View]: onView,
|
||||
@ -82,8 +99,16 @@ function WidgetHeader({
|
||||
[MenuItemKeys.Delete]: onDelete,
|
||||
[MenuItemKeys.Clone]: onClone,
|
||||
[MenuItemKeys.CreateAlerts]: onCreateAlertsHandler,
|
||||
[MenuItemKeys.Download]: onDownloadHandler,
|
||||
}),
|
||||
[onDelete, onEditHandler, onView, onClone, onCreateAlertsHandler],
|
||||
[
|
||||
onView,
|
||||
onEditHandler,
|
||||
onDelete,
|
||||
onClone,
|
||||
onCreateAlertsHandler,
|
||||
onDownloadHandler,
|
||||
],
|
||||
);
|
||||
|
||||
const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback(
|
||||
@ -128,6 +153,13 @@ function WidgetHeader({
|
||||
isVisible: headerMenuList?.includes(MenuItemKeys.Clone) || false,
|
||||
disabled: !editWidget,
|
||||
},
|
||||
{
|
||||
key: MenuItemKeys.Download,
|
||||
icon: <CloudDownloadOutlined />,
|
||||
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Download],
|
||||
isVisible: widget.panelTypes === PANEL_TYPES.TABLE,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: MenuItemKeys.Delete,
|
||||
icon: <DeleteOutlined />,
|
||||
@ -144,7 +176,13 @@ function WidgetHeader({
|
||||
disabled: false,
|
||||
},
|
||||
],
|
||||
[headerMenuList, queryResponse.isFetching, editWidget, deleteWidget],
|
||||
[
|
||||
headerMenuList,
|
||||
queryResponse.isFetching,
|
||||
editWidget,
|
||||
deleteWidget,
|
||||
widget.panelTypes,
|
||||
],
|
||||
);
|
||||
|
||||
const updatedMenuList = useMemo(() => generateMenuList(actions), [actions]);
|
||||
|
@ -19,4 +19,5 @@ export const isTWidgetOptions = (value: string): value is MenuItemKeys =>
|
||||
value === MenuItemKeys.Edit ||
|
||||
value === MenuItemKeys.Delete ||
|
||||
value === MenuItemKeys.Clone ||
|
||||
value === MenuItemKeys.CreateAlerts;
|
||||
value === MenuItemKeys.CreateAlerts ||
|
||||
value === MenuItemKeys.Download;
|
||||
|
@ -2,8 +2,12 @@ import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { Space, Tooltip } from 'antd';
|
||||
import { Events } from 'constants/events';
|
||||
import { QueryTable } from 'container/QueryTable';
|
||||
import { createTableColumnsFromQuery } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { memo, ReactNode, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
createTableColumnsFromQuery,
|
||||
RowData,
|
||||
} from 'lib/query/createTableColumnsFromQuery';
|
||||
import { get, set } from 'lodash-es';
|
||||
import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
|
||||
@ -15,6 +19,7 @@ function GridTableComponent({
|
||||
data,
|
||||
query,
|
||||
thresholds,
|
||||
tableProcessedDataRef,
|
||||
...props
|
||||
}: GridTableComponentProps): JSX.Element {
|
||||
const { t } = useTranslation(['valueGraph']);
|
||||
@ -27,6 +32,33 @@ function GridTableComponent({
|
||||
[data, query],
|
||||
);
|
||||
|
||||
const createDataInCorrectFormat = useCallback(
|
||||
(dataSource: RowData[]): RowData[] =>
|
||||
dataSource.map((d) => {
|
||||
const finalObject = {};
|
||||
const keys = Object.keys(d);
|
||||
keys.forEach((k) => {
|
||||
const label = get(
|
||||
columns.find((c) => get(c, 'dataIndex', '') === k) || {},
|
||||
'title',
|
||||
'',
|
||||
);
|
||||
if (label) {
|
||||
set(finalObject, label as string, d[k]);
|
||||
}
|
||||
});
|
||||
return finalObject as RowData;
|
||||
}),
|
||||
[columns],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (tableProcessedDataRef) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
tableProcessedDataRef.current = createDataInCorrectFormat(dataSource);
|
||||
}
|
||||
}, [createDataInCorrectFormat, dataSource, tableProcessedDataRef]);
|
||||
|
||||
const newColumnData = columns.map((e) => ({
|
||||
...e,
|
||||
render: (text: string): ReactNode => {
|
||||
|
@ -10,6 +10,7 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
export type GridTableComponentProps = {
|
||||
query: Query;
|
||||
thresholds?: ThresholdProps[];
|
||||
tableProcessedDataRef?: React.MutableRefObject<RowData[]>;
|
||||
} & Pick<LogsExplorerTableProps, 'data'> &
|
||||
Omit<TableProps<RowData>, 'columns' | 'dataSource'>;
|
||||
|
||||
|
@ -14,6 +14,7 @@ function PanelWrapper({
|
||||
onClickHandler,
|
||||
onDragSelect,
|
||||
selectedGraph,
|
||||
tableProcessedDataRef,
|
||||
}: PanelWrapperProps): JSX.Element {
|
||||
const Component = PanelTypeVsPanelWrapper[
|
||||
selectedGraph || widget.panelTypes
|
||||
@ -35,6 +36,7 @@ function PanelWrapper({
|
||||
onClickHandler={onClickHandler}
|
||||
onDragSelect={onDragSelect}
|
||||
selectedGraph={selectedGraph}
|
||||
tableProcessedDataRef={tableProcessedDataRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { PanelWrapperProps } from './panelWrapper.types';
|
||||
function TablePanelWrapper({
|
||||
widget,
|
||||
queryResponse,
|
||||
tableProcessedDataRef,
|
||||
}: PanelWrapperProps): JSX.Element {
|
||||
const panelData =
|
||||
queryResponse.data?.payload?.data.newResult.data.result || [];
|
||||
@ -15,6 +16,7 @@ function TablePanelWrapper({
|
||||
data={panelData}
|
||||
query={widget.query}
|
||||
thresholds={thresholds}
|
||||
tableProcessedDataRef={tableProcessedDataRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...GRID_TABLE_CONFIG}
|
||||
/>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { WidgetGraphComponentProps } from 'container/GridCardLayout/GridCard/types';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
@ -21,6 +22,7 @@ export type PanelWrapperProps = {
|
||||
onClickHandler?: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
selectedGraph?: PANEL_TYPES;
|
||||
tableProcessedDataRef?: React.MutableRefObject<RowData[]>;
|
||||
};
|
||||
|
||||
export type TooltipData = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user