mirror of https://github.com/buster-so/buster.git
remove additional context layers for dashboard
This commit is contained in:
parent
76254339e9
commit
ee6686448d
|
@ -6,19 +6,27 @@ import {
|
|||
dashboardsUpdateDashboard,
|
||||
dashboardsDeleteDashboard
|
||||
} from './requests';
|
||||
import type {
|
||||
DashboardsListRequest,
|
||||
DashboardCreateRequest,
|
||||
DashboardUpdateRequest,
|
||||
DashboardDeleteRequest
|
||||
} from '@/api/request_interfaces/dashboards/interfaces';
|
||||
import type { DashboardsListRequest } from '@/api/request_interfaces/dashboards/interfaces';
|
||||
import { dashboardQueryKeys } from '@/api/query_keys/dashboard';
|
||||
import { BusterDashboard } from '@/api/asset_interfaces/dashboard';
|
||||
import { useMemo } from 'react';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||
|
||||
export const useGetDashboardsList = (
|
||||
params: Omit<DashboardsListRequest, 'page_token' | 'page_size'>
|
||||
) => {
|
||||
const filters = useMemo(() => {
|
||||
return {
|
||||
...params,
|
||||
page_token: 0,
|
||||
page_size: 3000
|
||||
};
|
||||
}, [params]);
|
||||
|
||||
export const useGetDashboardsList = (params: DashboardsListRequest) => {
|
||||
return useQuery({
|
||||
...dashboardQueryKeys.dashboardGetList(params),
|
||||
queryFn: () => dashboardsGetList(params)
|
||||
...dashboardQueryKeys.dashboardGetList(filters),
|
||||
queryFn: () => dashboardsGetList(filters)
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -68,12 +76,44 @@ export const useUpdateDashboard = () => {
|
|||
|
||||
export const useDeleteDashboards = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { openConfirmModal } = useBusterNotifications();
|
||||
|
||||
const onDeleteDashboard = useMemoizedFn(
|
||||
async ({
|
||||
dashboardId,
|
||||
ignoreConfirm
|
||||
}: {
|
||||
dashboardId: string | string[];
|
||||
ignoreConfirm?: boolean;
|
||||
}) => {
|
||||
const method = () => {
|
||||
const ids = typeof dashboardId === 'string' ? [dashboardId] : dashboardId;
|
||||
dashboardsDeleteDashboard({ ids });
|
||||
};
|
||||
if (ignoreConfirm) {
|
||||
return method();
|
||||
}
|
||||
return await openConfirmModal({
|
||||
title: 'Delete Dashboard',
|
||||
content: 'Are you sure you want to delete this dashboard?',
|
||||
onOk: () => {
|
||||
method();
|
||||
},
|
||||
useReject: true
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: dashboardsDeleteDashboard,
|
||||
mutationFn: onDeleteDashboard,
|
||||
onMutate: (variables) => {
|
||||
const queryKey = dashboardQueryKeys.dashboardGetList({}).queryKey;
|
||||
queryClient.setQueryData(queryKey, (v) => {
|
||||
return v?.filter((t) => !variables.ids.includes(t.id)) || [];
|
||||
const ids =
|
||||
typeof variables.dashboardId === 'string'
|
||||
? [variables.dashboardId]
|
||||
: variables.dashboardId;
|
||||
return v?.filter((t) => !ids.includes(t.id)) || [];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,7 +8,9 @@ import { DashboardsListRequest } from '../request_interfaces/dashboards/interfac
|
|||
const dashboardGetList = (filters: Omit<DashboardsListRequest, 'page_token' | 'page_size'>) =>
|
||||
queryOptions<BusterDashboardListItem[]>({
|
||||
queryKey: ['dashboard', 'list', filters] as const,
|
||||
staleTime: 10 * 1000
|
||||
staleTime: 10 * 1000,
|
||||
initialData: [],
|
||||
initialDataUpdatedAt: 0
|
||||
});
|
||||
|
||||
const dashboardGetDashboard = (dashboardId: string) =>
|
||||
|
|
|
@ -39,7 +39,7 @@ export interface DashboardUnsubscribeRequest {
|
|||
*/
|
||||
export interface DashboardCreateRequest {
|
||||
/** The name of the dashboard */
|
||||
name: string;
|
||||
name?: string;
|
||||
/** Optional description of the dashboard */
|
||||
description?: string | null;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
import {
|
||||
useBusterDashboardContextSelector,
|
||||
useBusterDashboardListByFilter
|
||||
} from '@/context/Dashboards';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes/busterRoutes';
|
||||
|
@ -11,6 +7,7 @@ import { Dropdown, type DropdownProps } from '@/components/ui/dropdown/Dropdown'
|
|||
import { AppTooltip } from '@/components/ui/tooltip';
|
||||
import { Plus } from '@/components/ui/icons';
|
||||
import type { BusterMetric, BusterDashboardListItem } from '@/api/asset_interfaces';
|
||||
import { useCreateDashboard, useGetDashboardsList } from '@/api/buster_rest/dashboards';
|
||||
|
||||
export const SaveToDashboardDropdown: React.FC<{
|
||||
children: React.ReactNode;
|
||||
|
@ -18,10 +15,9 @@ export const SaveToDashboardDropdown: React.FC<{
|
|||
onSaveToDashboard: (dashboardId: string[]) => Promise<void>;
|
||||
onRemoveFromDashboard: (dashboardId: string) => void;
|
||||
}> = ({ children, onRemoveFromDashboard, onSaveToDashboard, selectedDashboards }) => {
|
||||
const onCreateNewDashboard = useBusterDashboardContextSelector((x) => x.onCreateNewDashboard);
|
||||
const isCreatingDashboard = useBusterDashboardContextSelector((x) => x.isCreatingDashboard);
|
||||
const { mutateAsync: createDashboard, isPending: isCreatingDashboard } = useCreateDashboard();
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
const { list: dashboardsList } = useBusterDashboardListByFilter({});
|
||||
const { data: dashboardsList } = useGetDashboardsList({});
|
||||
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
|
||||
|
@ -53,22 +49,13 @@ export const SaveToDashboardDropdown: React.FC<{
|
|||
);
|
||||
|
||||
const onClickNewDashboardButton = useMemoizedFn(async () => {
|
||||
const res = await onCreateNewDashboard({
|
||||
rerouteToDashboard: false
|
||||
});
|
||||
const res = await createDashboard({});
|
||||
|
||||
if (res?.id) {
|
||||
await onSaveToDashboard([res.id]);
|
||||
// await saveMetricToDashboard({
|
||||
// metricId,
|
||||
// dashboardIds: [res.id]
|
||||
// });
|
||||
}
|
||||
|
||||
if (res?.id) {
|
||||
if (res?.dashboard?.id) {
|
||||
await onSaveToDashboard([res.dashboard.id]);
|
||||
onChangePage({
|
||||
route: BusterRoutes.APP_DASHBOARD_ID,
|
||||
dashboardId: res.id
|
||||
dashboardId: res.dashboard.id
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import { useMemoizedFn } from '@/hooks';
|
|||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import { queryKeys } from '@/api/query_keys';
|
||||
import { useDashboardAssosciations } from './useDashboardAssosciations';
|
||||
import { useDashboardCreate } from './useDashboardCreate';
|
||||
import { useDashboardUpdateConfig } from './useDashboardUpdateConfig';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
|
@ -27,13 +26,8 @@ export const useBusterDashboards = () => {
|
|||
updateDashboardMutation
|
||||
});
|
||||
|
||||
const dashboardCreate = useDashboardCreate({
|
||||
onUpdateDashboard
|
||||
});
|
||||
|
||||
return {
|
||||
...dashboardAssosciations,
|
||||
...dashboardCreate,
|
||||
...dashboardUpdateConfig,
|
||||
getDashboardMemoized
|
||||
};
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { BusterDashboard, VerificationStatus } from '@/api/asset_interfaces';
|
||||
|
||||
export const defaultBusterDashboard: BusterDashboard = {
|
||||
id: '',
|
||||
config: {
|
||||
rows: []
|
||||
},
|
||||
created_at: '',
|
||||
deleted_at: '',
|
||||
description: '',
|
||||
name: '',
|
||||
updated_at: '',
|
||||
created_by: '',
|
||||
updated_by: '',
|
||||
public_expiry_date: null,
|
||||
publicly_accessible: false,
|
||||
password_secret_id: null,
|
||||
sharingKey: '',
|
||||
public_enabled_by: '',
|
||||
status: VerificationStatus.NOT_REQUESTED,
|
||||
public_password: null,
|
||||
version_number: 1,
|
||||
file: '',
|
||||
file_name: ''
|
||||
};
|
|
@ -1,66 +0,0 @@
|
|||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||
import { BusterRoutes } from '@/routes/busterRoutes';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
import { useCreateDashboard, useDeleteDashboards } from '@/api/buster_rest/dashboards';
|
||||
|
||||
export const useDashboardCreate = ({}: {}) => {
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
const queryClient = useQueryClient();
|
||||
const { mutateAsync: deleteDashboard, isPending: isDeletingDashboard } = useDeleteDashboards();
|
||||
const { openConfirmModal } = useBusterNotifications();
|
||||
|
||||
const { mutateAsync: createDashboard, isPending: isCreatingDashboard } = useCreateDashboard();
|
||||
|
||||
const onCreateNewDashboard = useMemoizedFn(
|
||||
async (newDashboard: {
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
rerouteToDashboard?: boolean;
|
||||
}) => {
|
||||
if (isCreatingDashboard) {
|
||||
return;
|
||||
}
|
||||
const { rerouteToDashboard, ...rest } = newDashboard;
|
||||
|
||||
const res = await createDashboard({ ...rest, name: rest.name || '' });
|
||||
|
||||
if (rerouteToDashboard) {
|
||||
onChangePage({
|
||||
route: BusterRoutes.APP_DASHBOARD_ID,
|
||||
dashboardId: res.dashboard.id
|
||||
});
|
||||
}
|
||||
|
||||
return res.dashboard;
|
||||
}
|
||||
);
|
||||
|
||||
const onDeleteDashboard = useMemoizedFn(
|
||||
async (dashboardId: string | string[], ignoreConfirm?: boolean) => {
|
||||
const method = () => {
|
||||
const ids = typeof dashboardId === 'string' ? [dashboardId] : dashboardId;
|
||||
deleteDashboard({ ids });
|
||||
};
|
||||
if (ignoreConfirm) {
|
||||
return method();
|
||||
}
|
||||
return await openConfirmModal({
|
||||
title: 'Delete Dashboard',
|
||||
content: 'Are you sure you want to delete this dashboard?',
|
||||
onOk: () => {
|
||||
method();
|
||||
},
|
||||
useReject: true
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
onCreateNewDashboard,
|
||||
isCreatingDashboard,
|
||||
onDeleteDashboard,
|
||||
isDeletingDashboard
|
||||
};
|
||||
};
|
|
@ -5,7 +5,6 @@ import {
|
|||
} from '@/api/asset_interfaces';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { create } from 'mutative';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useUpdateDashboard } from '@/api/buster_rest/dashboards';
|
||||
import type { DashboardUpdateRequest } from '@/api/request_interfaces/dashboards/interfaces';
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export * from './useBusterDashboardListByFilter';
|
|
@ -1,17 +0,0 @@
|
|||
import { useGetDashboardsList } from '@/api/buster_rest/dashboards';
|
||||
import { DashboardsListRequest } from '@/api/request_interfaces/dashboards/interfaces';
|
||||
|
||||
export const useBusterDashboardListByFilter = (
|
||||
filters: Omit<DashboardsListRequest, 'page_token' | 'page_size'>
|
||||
) => {
|
||||
const { data: dashboardsList, isFetched: isFetchedDashboardsList } = useGetDashboardsList({
|
||||
...filters,
|
||||
page_token: 0,
|
||||
page_size: 3000
|
||||
});
|
||||
|
||||
return {
|
||||
list: dashboardsList,
|
||||
isFetchedDashboardsList
|
||||
};
|
||||
};
|
|
@ -1,3 +1,2 @@
|
|||
export * from './DashboardProvider';
|
||||
export * from './DashboardListProvider';
|
||||
export * from './DashboardIndividualProvider';
|
||||
|
|
|
@ -9,6 +9,8 @@ import { AppSegmented, SegmentedItem } from '@/components/ui/segmented';
|
|||
import { useMemoizedFn } from '@/hooks';
|
||||
import { Plus } from '@/components/ui/icons';
|
||||
import type { DashboardsListRequest } from '@/api/request_interfaces/dashboards';
|
||||
import { useCreateDashboard } from '@/api/buster_rest/dashboards';
|
||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
|
||||
export const DashboardHeader: React.FC<{
|
||||
dashboardFilters: {
|
||||
|
@ -20,12 +22,8 @@ export const DashboardHeader: React.FC<{
|
|||
only_my_dashboards?: boolean;
|
||||
}) => void;
|
||||
}> = React.memo(({ dashboardFilters, onSetDashboardListFilters }) => {
|
||||
const onCreateNewDashboard = useBusterDashboardContextSelector(
|
||||
(state) => state.onCreateNewDashboard
|
||||
);
|
||||
const isCreatingDashboard = useBusterDashboardContextSelector(
|
||||
(state) => state.isCreatingDashboard
|
||||
);
|
||||
const { mutateAsync: createDashboard, isPending: isCreatingDashboard } = useCreateDashboard();
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
const dashboardTitle = 'Dashboards';
|
||||
const showFilters = true;
|
||||
|
||||
|
@ -42,7 +40,13 @@ export const DashboardHeader: React.FC<{
|
|||
);
|
||||
|
||||
const onClickNewDashboardButton = useMemoizedFn(async () => {
|
||||
await onCreateNewDashboard({ rerouteToDashboard: true });
|
||||
const res = await createDashboard({});
|
||||
if (res?.dashboard?.id) {
|
||||
onChangePage({
|
||||
route: BusterRoutes.APP_DASHBOARD_ID,
|
||||
dashboardId: res.dashboard.id
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useBusterDashboardContextSelector } from '@/context/Dashboards';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { formatDate } from '@/lib';
|
||||
import {
|
||||
|
@ -15,6 +14,8 @@ import { useMemoizedFn } from '@/hooks';
|
|||
import { DashboardSelectedOptionPopup } from './DashboardSelectedPopup';
|
||||
import type { BusterDashboardListItem } from '@/api/asset_interfaces';
|
||||
import { getShareStatus } from '@/components/features/metrics/StatusBadgeIndicator/helpers';
|
||||
import { useCreateDashboard } from '@/api/buster_rest/dashboards';
|
||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
|
||||
const columns: BusterListColumn[] = [
|
||||
{
|
||||
|
@ -58,12 +59,8 @@ export const DashboardListContent: React.FC<{
|
|||
dashboardsList: BusterDashboardListItem[];
|
||||
className?: string;
|
||||
}> = React.memo(({ loading, dashboardsList, className = '' }) => {
|
||||
const onCreateNewDashboard = useBusterDashboardContextSelector(
|
||||
(state) => state.onCreateNewDashboard
|
||||
);
|
||||
const isCreatingDashboard = useBusterDashboardContextSelector(
|
||||
(state) => state.isCreatingDashboard
|
||||
);
|
||||
const { mutateAsync: createDashboard, isPending: isCreatingDashboard } = useCreateDashboard();
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
const [selectedDashboardIds, setSelectedDashboardIds] = useState<string[]>([]);
|
||||
|
||||
const rows: BusterListRow[] = useMemo(() => {
|
||||
|
@ -80,7 +77,13 @@ export const DashboardListContent: React.FC<{
|
|||
}, [dashboardsList]);
|
||||
|
||||
const onClickEmptyState = useMemoizedFn(async () => {
|
||||
await onCreateNewDashboard({ rerouteToDashboard: true });
|
||||
const res = await createDashboard({});
|
||||
if (res?.dashboard?.id) {
|
||||
onChangePage({
|
||||
route: BusterRoutes.APP_DASHBOARD_ID,
|
||||
dashboardId: res.dashboard.id
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
import React, { useState } from 'react';
|
||||
import { DashboardHeader } from './DashboardHeader';
|
||||
import { DashboardListContent } from './DashboardListContent';
|
||||
import { useBusterDashboardListByFilter } from '@/context/Dashboards';
|
||||
import { AppPageLayout } from '@/components/ui/layouts';
|
||||
import { useGetDashboardsList } from '@/api/buster_rest/dashboards';
|
||||
|
||||
export const DashboardListController: React.FC = () => {
|
||||
const [dashboardListFilters, setDashboardListFilters] = useState<{
|
||||
shared_with_me?: boolean;
|
||||
only_my_dashboards?: boolean;
|
||||
}>({});
|
||||
const { list, isFetchedDashboardsList } = useBusterDashboardListByFilter(dashboardListFilters);
|
||||
const { data: dashboardsList, isFetched: isFetchedDashboardsList } =
|
||||
useGetDashboardsList(dashboardListFilters);
|
||||
|
||||
return (
|
||||
<AppPageLayout
|
||||
|
@ -21,7 +22,7 @@ export const DashboardListController: React.FC = () => {
|
|||
onSetDashboardListFilters={setDashboardListFilters}
|
||||
/>
|
||||
}>
|
||||
<DashboardListContent loading={!isFetchedDashboardsList} dashboardsList={list || []} />
|
||||
<DashboardListContent loading={!isFetchedDashboardsList} dashboardsList={dashboardsList} />
|
||||
</AppPageLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,7 +2,6 @@ import React, { useState } from 'react';
|
|||
import { BusterListSelectedOptionPopupContainer } from '@/components/ui/list';
|
||||
import { Dropdown, DropdownItems } from '@/components/ui/dropdown';
|
||||
import { Button } from '@/components/ui/buttons';
|
||||
import { useUserConfigContextSelector } from '@/context/Users';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||
import { SaveToCollectionsDropdown } from '@/components/features/dropdowns/SaveToCollectionsDropdown';
|
||||
|
@ -15,6 +14,7 @@ import {
|
|||
useGetUserFavorites
|
||||
} from '@/api/buster_rest/users';
|
||||
import { ShareAssetType } from '@/api/asset_interfaces/share';
|
||||
import { useDeleteDashboards } from '@/api/buster_rest/dashboards';
|
||||
|
||||
export const DashboardSelectedOptionPopup: React.FC<{
|
||||
selectedRowKeys: string[];
|
||||
|
@ -100,7 +100,7 @@ const DeleteButton: React.FC<{
|
|||
selectedRowKeys: string[];
|
||||
onSelectChange: (selectedRowKeys: string[]) => void;
|
||||
}> = ({ selectedRowKeys, onSelectChange }) => {
|
||||
const onDeleteDashboard = useBusterDashboardContextSelector((state) => state.onDeleteDashboard);
|
||||
const { mutateAsync: deleteDashboard, isPending: isDeletingDashboard } = useDeleteDashboards();
|
||||
const { openConfirmModal } = useBusterNotifications();
|
||||
|
||||
const onDeleteClick = useMemoizedFn(async () => {
|
||||
|
@ -108,7 +108,7 @@ const DeleteButton: React.FC<{
|
|||
title: 'Delete dashboard',
|
||||
content: 'Are you sure you want to delete these dashboards?',
|
||||
onOk: async () => {
|
||||
await onDeleteDashboard(selectedRowKeys, true);
|
||||
await deleteDashboard({ dashboardId: selectedRowKeys });
|
||||
onSelectChange([]);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Dots, Plus, Trash } from '@/components/ui/icons';
|
|||
import { useBusterDashboardContextSelector } from '@/context/Dashboards';
|
||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
import { BusterRoutes } from '@/routes';
|
||||
import { useDeleteDashboards } from '@/api/buster_rest/dashboards';
|
||||
|
||||
export const DashboardContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(
|
||||
() => {
|
||||
|
@ -50,7 +51,7 @@ const AddContentToDashboardButton = React.memo(() => {
|
|||
AddContentToDashboardButton.displayName = 'AddContentToDashboardButton';
|
||||
|
||||
const ThreeDotMenu = React.memo(({ dashboardId }: { dashboardId: string }) => {
|
||||
const onDeleteDashboard = useBusterDashboardContextSelector((x) => x.onDeleteDashboard);
|
||||
const { mutateAsync: deleteDashboard, isPending: isDeletingDashboard } = useDeleteDashboards();
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
|
||||
const items: DropdownItems = useMemo(() => {
|
||||
|
@ -60,12 +61,12 @@ const ThreeDotMenu = React.memo(({ dashboardId }: { dashboardId: string }) => {
|
|||
value: 'delete',
|
||||
icon: <Trash />,
|
||||
onClick: async () => {
|
||||
await onDeleteDashboard(dashboardId);
|
||||
await deleteDashboard({ dashboardId });
|
||||
onChangePage({ route: BusterRoutes.APP_DASHBOARDS });
|
||||
}
|
||||
}
|
||||
];
|
||||
}, [dashboardId, onDeleteDashboard, onChangePage]);
|
||||
}, [dashboardId, deleteDashboard, onChangePage]);
|
||||
|
||||
return (
|
||||
<Dropdown items={items}>
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
VerificationStatus
|
||||
} from '@/api/asset_interfaces';
|
||||
import { ShareRole } from '@/api/asset_interfaces';
|
||||
import { createMockMetric } from '../../../mocks/metric';
|
||||
import { createMockMetric } from './metric';
|
||||
|
||||
interface DashboardMockResponse {
|
||||
dashboard: BusterDashboard;
|
Loading…
Reference in New Issue