superset-frontend/src/features/home/DashboardTable.tsx
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { useEffect, useMemo, useState } from 'react';
import { SupersetClient, t } from '@superset-ui/core';
import { filter } from 'lodash';
import { useFavoriteStatus, useListViewResource } from 'src/views/CRUD/hooks';
import { Dashboard, DashboardTableProps, TableTab } from 'src/views/CRUD/types';
import handleResourceExport from 'src/utils/export';
import { useHistory } from 'react-router-dom';
import {
getItem,
LocalStorageKeys,
setItem,
} from 'src/utils/localStorageHelpers';
import { LoadingCards } from 'src/pages/Home';
import {
CardContainer,
createErrorHandler,
getFilterValues,
PAGE_SIZE,
handleDashboardDelete,
} from 'src/views/CRUD/utils';
import withToasts from 'src/components/MessageToasts/withToasts';
import Loading from 'src/components/Loading';
import DeleteModal from 'src/components/DeleteModal';
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
import DashboardCard from 'src/features/dashboards/DashboardCard';
import EmptyState from './EmptyState';
import SubMenu from './SubMenu';
import { WelcomeTable } from './types';
function DashboardTable({
user,
addDangerToast,
addSuccessToast,
mine,
showThumbnails,
otherTabData,
otherTabFilters,
otherTabTitle,
}: DashboardTableProps) {
const history = useHistory();
const defaultTab = getItem(
LocalStorageKeys.HomepageDashboardFilter,
TableTab.Other,
);
const filteredOtherTabData = filter(
otherTabData,
obj => !('viz_type' in obj),
);
const {
state: { loading, resourceCollection: dashboards },
setResourceCollection: setDashboards,
hasPerm,
refreshData,
fetchData,
} = useListViewResource<Dashboard>(
'dashboard',
t('dashboard'),
addDangerToast,
true,
defaultTab === TableTab.Mine ? mine : filteredOtherTabData,
[],
false,
);
const dashboardIds = useMemo(() => dashboards.map(c => c.id), [dashboards]);
const [saveFavoriteStatus, favoriteStatus] = useFavoriteStatus(
'dashboard',
dashboardIds,
addDangerToast,
);
const [editModal, setEditModal] = useState<Dashboard>();
const [activeTab, setActiveTab] = useState(defaultTab);
const [preparingExport, setPreparingExport] = useState<boolean>(false);
const [loaded, setLoaded] = useState<boolean>(false);
const [dashboardToDelete, setDashboardToDelete] = useState<Dashboard | null>(
null,
);
const getData = (tab: TableTab) =>
fetchData({
pageIndex: 0,
pageSize: PAGE_SIZE,
sortBy: [
{
id: 'changed_on_delta_humanized',
desc: true,
},
],
filters: getFilterValues(
tab,
WelcomeTable.Dashboards,
user,
otherTabFilters,
),
});
useEffect(() => {
if (loaded || activeTab === TableTab.Favorite) {
getData(activeTab);
}
setLoaded(true);
}, [activeTab]);
const handleBulkDashboardExport = (dashboardsToExport: Dashboard[]) => {
const ids = dashboardsToExport.map(({ id }) => id);
handleResourceExport('dashboard', ids, () => {
setPreparingExport(false);
});
setPreparingExport(true);
};
const handleDashboardEdit = (edits: Dashboard) =>
SupersetClient.get({
endpoint: `/api/v1/dashboard/${edits.id}`,
}).then(
({ json = {} }) => {
setDashboards(
dashboards.map(dashboard => {
if (dashboard.id === json.id) {
return json.result;
}
return dashboard;
}),
);
},
createErrorHandler(errMsg =>
addDangerToast(
t('An error occurred while fetching dashboards: %s', errMsg),
),
),
);
const menuTabs = [
{
name: TableTab.Favorite,
label: t('Favorite'),
onClick: () => {
setActiveTab(TableTab.Favorite);
setItem(LocalStorageKeys.HomepageDashboardFilter, TableTab.Favorite);
},
},
{
name: TableTab.Mine,
label: t('Mine'),
onClick: () => {
setActiveTab(TableTab.Mine);
setItem(LocalStorageKeys.HomepageDashboardFilter, TableTab.Mine);
},
},
];
if (otherTabData) {
menuTabs.push({
name: TableTab.Other,
label: otherTabTitle,
onClick: () => {
setActiveTab(TableTab.Other);
setItem(LocalStorageKeys.HomepageDashboardFilter, TableTab.Other);
},
});
}
if (loading) return <LoadingCards cover={showThumbnails} />;
return (
<>
<SubMenu
activeChild={activeTab}
tabs={menuTabs}
buttons={[
{
name: (
<>
<i className="fa fa-plus" />
{t('Dashboard')}
</>
),
buttonStyle: 'tertiary',
onClick: () => {
window.location.assign('/dashboard/new');
},
},
{
name: t('View All ยป'),
buttonStyle: 'link',
onClick: () => {
const target =
activeTab === TableTab.Favorite
? `/dashboard/list/?filters=(favorite:(label:${t(
'Yes',
)},value:!t))`
: '/dashboard/list/';
history.push(target);
},
},
]}
/>
{editModal && (
<PropertiesModal
dashboardId={editModal?.id}
show
onHide={() => setEditModal(undefined)}
onSubmit={handleDashboardEdit}
/>
)}
{dashboardToDelete && (
<DeleteModal
description={
<>
{t('Are you sure you want to delete')}{' '}
<b>{dashboardToDelete.dashboard_title}</b>?
</>
}
onConfirm={() => {
handleDashboardDelete(
dashboardToDelete,
refreshData,
addSuccessToast,
addDangerToast,
activeTab,
user?.userId,
);
setDashboardToDelete(null);
}}
onHide={() => setDashboardToDelete(null)}
open={!!dashboardToDelete}
title={t('Please confirm')}
/>
)}
{dashboards.length > 0 && (
<CardContainer showThumbnails={showThumbnails}>
{dashboards.map(e => (
<DashboardCard
key={e.id}
dashboard={e}
hasPerm={hasPerm}
bulkSelectEnabled={false}
showThumbnails={showThumbnails}
userId={user?.userId}
loading={loading}
openDashboardEditModal={(dashboard: Dashboard) =>
setEditModal(dashboard)
}
saveFavoriteStatus={saveFavoriteStatus}
favoriteStatus={favoriteStatus[e.id]}
handleBulkDashboardExport={handleBulkDashboardExport}
onDelete={dashboard => setDashboardToDelete(dashboard)}
/>
))}
</CardContainer>
)}
{dashboards.length === 0 && (
<EmptyState tableName={WelcomeTable.Dashboards} tab={activeTab} />
)}
{preparingExport && <Loading />}
</>
);
}
export default withToasts(DashboardTable);