diff --git a/frontend/src/container/ListOfDashboard/DashboardsList.tsx b/frontend/src/container/ListOfDashboard/DashboardsList.tsx index 885ed87e46..902421052c 100644 --- a/frontend/src/container/ListOfDashboard/DashboardsList.tsx +++ b/frontend/src/container/ListOfDashboard/DashboardsList.tsx @@ -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'; @@ -44,6 +46,7 @@ import { EllipsisVertical, Expand, ExternalLink, + FileJson, Github, HdmiPort, LayoutGrid, @@ -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'; @@ -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, })) || []; @@ -413,6 +427,15 @@ function DashboardsList(): JSX.Element { }); }; + const handleJsonExport = (event: React.MouseEvent): void => { + event.stopPropagation(); + event.preventDefault(); + downloadObjectAsJson( + sanitizeDashboardData({ ...dashboard, title: dashboard.name }), + dashboard.name, + ); + }; + return (
@@ -486,6 +509,14 @@ function DashboardsList(): JSX.Element { > Copy Link +
{ e.stopPropagation(); e.preventDefault(); @@ -1068,6 +1100,11 @@ export interface Data { isLocked: boolean; id: string; image?: string; + widgets?: Array; + layout?: Layout[]; + panelMap?: Record; + variables: Record; + version?: string; } export default DashboardsList; diff --git a/frontend/src/container/NewDashboard/DashboardDescription/index.tsx b/frontend/src/container/NewDashboard/DashboardDescription/index.tsx index 151fba7609..7845cf818e 100644 --- a/frontend/src/container/NewDashboard/DashboardDescription/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardDescription/index.tsx @@ -65,7 +65,7 @@ interface DashboardDescriptionProps { handle: FullScreenHandle; } -function sanitizeDashboardData( +export function sanitizeDashboardData( selectedData: DashboardData, ): Omit { if (!selectedData?.variables) { diff --git a/frontend/src/pages/DashboardsListPage/__tests__/DashboardListPage.test.tsx b/frontend/src/pages/DashboardsListPage/__tests__/DashboardListPage.test.tsx index 98bd40ef62..d075316422 100644 --- a/frontend/src/pages/DashboardsListPage/__tests__/DashboardListPage.test.tsx +++ b/frontend/src/pages/DashboardsListPage/__tests__/DashboardListPage.test.tsx @@ -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(), @@ -204,4 +212,26 @@ describe('dashboard list page', () => { ), ); }); + + it('ensure that the export JSON popover action works correctly', async () => { + const { getByText, getAllByTestId } = render(); + + 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, + }), + ); + }); });