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:
Vikrant Gupta 2024-05-24 15:54:36 +05:30 committed by GitHub
parent 52e4c2d8ff
commit 76b1e40cbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 95 additions and 6 deletions

View File

@ -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>

View File

@ -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;

View File

@ -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>
)}

View File

@ -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',
};

View File

@ -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]);

View File

@ -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;

View File

@ -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 => {

View File

@ -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'>;

View File

@ -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}
/>
);
}

View File

@ -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}
/>

View File

@ -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 = {