Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add functionality to export dashboard as json from listing page #6595

Merged
merged 9 commits into from
Dec 16, 2024
Merged
39 changes: 38 additions & 1 deletion frontend/src/container/ListOfDashboard/DashboardsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { AxiosError } from 'axios';
import cx from 'classnames';
import { ENTITY_VERSION_V4 } from 'constants/app';
import ROUTES from 'constants/routes';
import { sanitizeDashboardData } from 'container/NewDashboard/DashboardDescription';
import { downloadObjectAsJson } from 'container/NewDashboard/DashboardDescription/utils';
import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils';
import dayjs from 'dayjs';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
Expand All @@ -44,6 +46,7 @@ import {
EllipsisVertical,
Expand,
ExternalLink,
FileJson,
Github,
HdmiPort,
LayoutGrid,
Expand All @@ -67,12 +70,18 @@ import {
useRef,
useState,
} from 'react';
import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { generatePath, Link } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { AppState } from 'store/reducers';
import { Dashboard } from 'types/api/dashboard/getAll';
import {
Dashboard,
IDashboardVariable,
WidgetRow,
Widgets,
} from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import { isCloudUser } from 'utils/app';

Expand Down Expand Up @@ -261,6 +270,11 @@ function DashboardsList(): JSX.Element {
isLocked: !!e.isLocked || false,
lastUpdatedBy: e.updated_by,
image: e.data.image || Base64Icons[0],
variables: e.data.variables,
widgets: e.data.widgets,
layout: e.data.layout,
panelMap: e.data.panelMap,
version: e.data.version,
refetchDashboardList,
})) || [];

Expand Down Expand Up @@ -413,6 +427,15 @@ function DashboardsList(): JSX.Element {
});
};

const handleJsonExport = (event: React.MouseEvent<HTMLElement>): void => {
amlannandy marked this conversation as resolved.
Show resolved Hide resolved
event.stopPropagation();
event.preventDefault();
downloadObjectAsJson(
sanitizeDashboardData({ ...dashboard, title: dashboard.name }),
dashboard.name,
);
};

return (
<div className="dashboard-list-item" onClick={onClickHandler}>
<div className="title-with-action">
Expand Down Expand Up @@ -486,6 +509,14 @@ function DashboardsList(): JSX.Element {
>
Copy Link
</Button>
<Button
type="text"
className="action-btn"
icon={<FileJson size={12} />}
onClick={handleJsonExport}
>
Export JSON
</Button>
</section>
<section className="section-2">
<DeleteButton
Expand All @@ -504,6 +535,7 @@ function DashboardsList(): JSX.Element {
<EllipsisVertical
className="dashboard-action-icon"
size={14}
data-testid="dashboard-action-icon"
amlannandy marked this conversation as resolved.
Show resolved Hide resolved
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
Expand Down Expand Up @@ -1068,6 +1100,11 @@ export interface Data {
isLocked: boolean;
id: string;
image?: string;
widgets?: Array<WidgetRow | Widgets>;
layout?: Layout[];
panelMap?: Record<string, { widgets: Layout[]; collapsed: boolean }>;
variables: Record<string, IDashboardVariable>;
version?: string;
amlannandy marked this conversation as resolved.
Show resolved Hide resolved
}

export default DashboardsList;
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ interface DashboardDescriptionProps {
handle: FullScreenHandle;
}

function sanitizeDashboardData(
export function sanitizeDashboardData(
selectedData: DashboardData,
): Omit<DashboardData, 'uuid'> {
if (!selectedData?.variables) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
/* eslint-disable sonarjs/no-duplicate-string */
import ROUTES from 'constants/routes';
import DashboardsList from 'container/ListOfDashboard';
import { dashboardEmptyState } from 'mocks-server/__mockdata__/dashboards';
import * as dashboardUtils from 'container/NewDashboard/DashboardDescription';
import {
dashboardEmptyState,
dashboardSuccessResponse,
} from 'mocks-server/__mockdata__/dashboards';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { MemoryRouter, useLocation } from 'react-router-dom';
import { fireEvent, render, waitFor } from 'tests/test-utils';

jest.mock('container/NewDashboard/DashboardDescription', () => ({
sanitizeDashboardData: jest.fn(),
}));

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: jest.fn(),
Expand Down Expand Up @@ -204,4 +212,26 @@ describe('dashboard list page', () => {
),
);
});

it('ensure that the export JSON popover action works correctly', async () => {
const { getByText, getAllByTestId } = render(<DashboardsList />);

await waitFor(() => {
const popovers = getAllByTestId('dashboard-action-icon');
expect(popovers).toHaveLength(dashboardSuccessResponse.data.length);
fireEvent.click([...popovers[0].children][0]);
});

const exportJsonBtn = getByText('Export JSON');
expect(exportJsonBtn).toBeInTheDocument();
fireEvent.click(exportJsonBtn);
const firstDashboardData = dashboardSuccessResponse.data[0];
expect(dashboardUtils.sanitizeDashboardData).toHaveBeenCalledWith(
expect.objectContaining({
id: firstDashboardData.uuid,
title: firstDashboardData.data.title,
createdAt: firstDashboardData.created_at,
}),
);
});
});
Loading