mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
feat: show spinner on exports (#15107)
* feat: show spinner on exports * Set cookie only if token is passed * Use iframe * Small fixes * Fix lint * Remove stale test * Add explicit type
This commit is contained in:
48
superset-frontend/src/utils/export.ts
Normal file
48
superset-frontend/src/utils/export.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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 parseCookie from 'src/utils/parseCookie';
|
||||
import rison from 'rison';
|
||||
import shortid from 'shortid';
|
||||
|
||||
export default function handleResourceExport(
|
||||
resource: string,
|
||||
ids: number[],
|
||||
done: () => void,
|
||||
interval = 200,
|
||||
): void {
|
||||
const token = shortid.generate();
|
||||
const url = `/api/v1/${resource}/export/?q=${rison.encode(
|
||||
ids,
|
||||
)}&token=${token}`;
|
||||
|
||||
// create new iframe for export
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = url;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
const timer = window.setInterval(() => {
|
||||
const cookie: { [cookieId: string]: string } = parseCookie();
|
||||
if (cookie[token] === 'done') {
|
||||
window.clearInterval(timer);
|
||||
document.body.removeChild(iframe);
|
||||
done();
|
||||
}
|
||||
}, interval);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import Label from 'src/components/Label';
|
||||
import { Dropdown, Menu } from 'src/common/components';
|
||||
import FaveStar from 'src/components/FaveStar';
|
||||
import FacePile from 'src/components/FacePile';
|
||||
import { handleChartDelete, handleBulkChartExport, CardStyles } from '../utils';
|
||||
import { handleChartDelete, CardStyles } from '../utils';
|
||||
|
||||
interface ChartCardProps {
|
||||
chart: Chart;
|
||||
@@ -45,6 +45,7 @@ interface ChartCardProps {
|
||||
chartFilter?: string;
|
||||
userId?: number;
|
||||
showThumbnails?: boolean;
|
||||
handleBulkChartExport: (chartsToExport: Chart[]) => void;
|
||||
}
|
||||
|
||||
export default function ChartCard({
|
||||
@@ -61,6 +62,7 @@ export default function ChartCard({
|
||||
favoriteStatus,
|
||||
chartFilter,
|
||||
userId,
|
||||
handleBulkChartExport,
|
||||
}: ChartCardProps) {
|
||||
const canEdit = hasPerm('can_write');
|
||||
const canDelete = hasPerm('can_write');
|
||||
|
||||
@@ -29,7 +29,6 @@ import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import {
|
||||
createErrorHandler,
|
||||
createFetchRelated,
|
||||
handleBulkChartExport,
|
||||
handleChartDelete,
|
||||
} from 'src/views/CRUD/utils';
|
||||
import {
|
||||
@@ -37,6 +36,7 @@ import {
|
||||
useFavoriteStatus,
|
||||
useListViewResource,
|
||||
} from 'src/views/CRUD/hooks';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
||||
import FaveStar from 'src/components/FaveStar';
|
||||
@@ -47,6 +47,7 @@ import ListView, {
|
||||
ListViewProps,
|
||||
SelectOption,
|
||||
} from 'src/components/ListView';
|
||||
import Loading from 'src/components/Loading';
|
||||
import { getFromLocalStorage } from 'src/utils/localStorageHelpers';
|
||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||
import PropertiesModal from 'src/explore/components/PropertiesModal';
|
||||
@@ -156,6 +157,7 @@ function ChartList(props: ChartListProps) {
|
||||
|
||||
const [importingChart, showImportModal] = useState<boolean>(false);
|
||||
const [passwordFields, setPasswordFields] = useState<string[]>([]);
|
||||
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
||||
|
||||
const openChartImportModal = () => {
|
||||
showImportModal(true);
|
||||
@@ -177,6 +179,14 @@ function ChartList(props: ChartListProps) {
|
||||
hasPerm('can_read') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT);
|
||||
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||
|
||||
const handleBulkChartExport = (chartsToExport: Chart[]) => {
|
||||
const ids = chartsToExport.map(({ id }) => id);
|
||||
handleResourceExport('chart', ids, () => {
|
||||
setPreparingExport(false);
|
||||
});
|
||||
setPreparingExport(true);
|
||||
};
|
||||
|
||||
function handleBulkChartDelete(chartsToDelete: Chart[]) {
|
||||
SupersetClient.delete({
|
||||
endpoint: `/api/v1/chart/?q=${rison.encode(
|
||||
@@ -540,6 +550,7 @@ function ChartList(props: ChartListProps) {
|
||||
loading={loading}
|
||||
favoriteStatus={favoriteStatus[chart.id]}
|
||||
saveFavoriteStatus={saveFavoriteStatus}
|
||||
handleBulkChartExport={handleBulkChartExport}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -653,6 +664,7 @@ function ChartList(props: ChartListProps) {
|
||||
passwordFields={passwordFields}
|
||||
setPasswordFields={setPasswordFields}
|
||||
/>
|
||||
{preparingExport && <Loading />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,11 +19,7 @@
|
||||
import React from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { t } from '@superset-ui/core';
|
||||
import {
|
||||
handleDashboardDelete,
|
||||
handleBulkDashboardExport,
|
||||
CardStyles,
|
||||
} from 'src/views/CRUD/utils';
|
||||
import { handleDashboardDelete, CardStyles } from 'src/views/CRUD/utils';
|
||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||
import { Dropdown, Menu } from 'src/common/components';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
@@ -49,6 +45,7 @@ interface DashboardCardProps {
|
||||
dashboardFilter?: string;
|
||||
userId?: number;
|
||||
showThumbnails?: boolean;
|
||||
handleBulkDashboardExport: (dashboardsToExport: Dashboard[]) => void;
|
||||
}
|
||||
|
||||
function DashboardCard({
|
||||
@@ -64,6 +61,7 @@ function DashboardCard({
|
||||
favoriteStatus,
|
||||
saveFavoriteStatus,
|
||||
showThumbnails,
|
||||
handleBulkDashboardExport,
|
||||
}: DashboardCardProps) {
|
||||
const history = useHistory();
|
||||
const canEdit = hasPerm('can_write');
|
||||
|
||||
@@ -25,10 +25,11 @@ import {
|
||||
createFetchRelated,
|
||||
createErrorHandler,
|
||||
handleDashboardDelete,
|
||||
handleBulkDashboardExport,
|
||||
} from 'src/views/CRUD/utils';
|
||||
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
import Loading from 'src/components/Loading';
|
||||
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
||||
import ListView, {
|
||||
ListViewProps,
|
||||
@@ -123,6 +124,7 @@ function DashboardList(props: DashboardListProps) {
|
||||
|
||||
const [importingDashboard, showImportModal] = useState<boolean>(false);
|
||||
const [passwordFields, setPasswordFields] = useState<string[]>([]);
|
||||
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
||||
|
||||
const openDashboardImportModal = () => {
|
||||
showImportModal(true);
|
||||
@@ -170,6 +172,14 @@ function DashboardList(props: DashboardListProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const handleBulkDashboardExport = (dashboardsToExport: Dashboard[]) => {
|
||||
const ids = dashboardsToExport.map(({ id }) => id);
|
||||
handleResourceExport('dashboard', ids, () => {
|
||||
setPreparingExport(false);
|
||||
});
|
||||
setPreparingExport(true);
|
||||
};
|
||||
|
||||
function handleBulkDashboardDelete(dashboardsToDelete: Dashboard[]) {
|
||||
return SupersetClient.delete({
|
||||
endpoint: `/api/v1/dashboard/?q=${rison.encode(
|
||||
@@ -487,6 +497,7 @@ function DashboardList(props: DashboardListProps) {
|
||||
openDashboardEditModal={openDashboardEditModal}
|
||||
saveFavoriteStatus={saveFavoriteStatus}
|
||||
favoriteStatus={favoriteStatus[dashboard.id]}
|
||||
handleBulkDashboardExport={handleBulkDashboardExport}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -605,6 +616,7 @@ function DashboardList(props: DashboardListProps) {
|
||||
passwordFields={passwordFields}
|
||||
setPasswordFields={setPasswordFields}
|
||||
/>
|
||||
{preparingExport && <Loading />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import { SupersetClient, t, styled } from '@superset-ui/core';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import rison from 'rison';
|
||||
import Loading from 'src/components/Loading';
|
||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||
import { useListViewResource } from 'src/views/CRUD/hooks';
|
||||
import { createErrorHandler } from 'src/views/CRUD/utils';
|
||||
@@ -30,6 +30,7 @@ import Icons from 'src/components/Icons';
|
||||
import ListView, { FilterOperator, Filters } from 'src/components/ListView';
|
||||
import { commonMenuData } from 'src/views/CRUD/data/common';
|
||||
import ImportModelsModal from 'src/components/ImportModal/index';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
import DatabaseModal from './DatabaseModal';
|
||||
|
||||
import { DatabaseObject } from './types';
|
||||
@@ -97,6 +98,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
||||
);
|
||||
const [importingDatabase, showImportModal] = useState<boolean>(false);
|
||||
const [passwordFields, setPasswordFields] = useState<string[]>([]);
|
||||
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
||||
|
||||
const openDatabaseImportModal = () => {
|
||||
showImportModal(true);
|
||||
@@ -203,9 +205,14 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
||||
}
|
||||
|
||||
function handleDatabaseExport(database: DatabaseObject) {
|
||||
return window.location.assign(
|
||||
`/api/v1/database/export/?q=${rison.encode([database.id])}`,
|
||||
);
|
||||
if (database.id === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleResourceExport('database', [database.id], () => {
|
||||
setPreparingExport(false);
|
||||
});
|
||||
setPreparingExport(true);
|
||||
}
|
||||
|
||||
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||
@@ -469,6 +476,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
||||
passwordFields={passwordFields}
|
||||
setPasswordFields={setPasswordFields}
|
||||
/>
|
||||
{preparingExport && <Loading />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,11 +33,13 @@ import { useListViewResource } from 'src/views/CRUD/hooks';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
import DatasourceModal from 'src/datasource/DatasourceModal';
|
||||
import DeleteModal from 'src/components/DeleteModal';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
import ListView, {
|
||||
ListViewProps,
|
||||
Filters,
|
||||
FilterOperator,
|
||||
} from 'src/components/ListView';
|
||||
import Loading from 'src/components/Loading';
|
||||
import SubMenu, {
|
||||
SubMenuProps,
|
||||
ButtonProps,
|
||||
@@ -131,6 +133,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||
|
||||
const [importingDataset, showImportModal] = useState<boolean>(false);
|
||||
const [passwordFields, setPasswordFields] = useState<string[]>([]);
|
||||
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
||||
|
||||
const openDatasetImportModal = () => {
|
||||
showImportModal(true);
|
||||
@@ -547,12 +550,13 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const handleBulkDatasetExport = (datasetsToExport: Dataset[]) =>
|
||||
window.location.assign(
|
||||
`/api/v1/dataset/export/?q=${rison.encode(
|
||||
datasetsToExport.map(({ id }) => id),
|
||||
)}`,
|
||||
);
|
||||
const handleBulkDatasetExport = (datasetsToExport: Dataset[]) => {
|
||||
const ids = datasetsToExport.map(({ id }) => id);
|
||||
handleResourceExport('dataset', ids, () => {
|
||||
setPreparingExport(false);
|
||||
});
|
||||
setPreparingExport(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -682,6 +686,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||
passwordFields={passwordFields}
|
||||
setPasswordFields={setPasswordFields}
|
||||
/>
|
||||
{preparingExport && <Loading />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,7 +26,6 @@ import { render, screen, cleanup, waitFor } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { handleBulkSavedQueryExport } from 'src/views/CRUD/utils';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
import SavedQueryList from 'src/views/CRUD/data/savedquery/SavedQueryList';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
@@ -285,14 +284,6 @@ describe('RTL', () => {
|
||||
expect(exportTooltip).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('runs handleBulkSavedQueryExport when export is clicked', () => {
|
||||
// Grab Export action button and mock mouse clicking it
|
||||
const exportActionButton = screen.getAllByRole('button')[18];
|
||||
userEvent.click(exportActionButton);
|
||||
|
||||
expect(handleBulkSavedQueryExport).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders an import button in the submenu', () => {
|
||||
// Grab and assert that import saved query button is visible
|
||||
const importButton = screen.getByTestId('import-button');
|
||||
|
||||
@@ -25,12 +25,12 @@ import {
|
||||
createFetchRelated,
|
||||
createFetchDistinct,
|
||||
createErrorHandler,
|
||||
handleBulkSavedQueryExport,
|
||||
} from 'src/views/CRUD/utils';
|
||||
import Popover from 'src/components/Popover';
|
||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||
import { useListViewResource } from 'src/views/CRUD/hooks';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
import SubMenu, {
|
||||
SubMenuProps,
|
||||
ButtonProps,
|
||||
@@ -40,6 +40,7 @@ import ListView, {
|
||||
Filters,
|
||||
FilterOperator,
|
||||
} from 'src/components/ListView';
|
||||
import Loading from 'src/components/Loading';
|
||||
import DeleteModal from 'src/components/DeleteModal';
|
||||
import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
@@ -117,6 +118,7 @@ function SavedQueryList({
|
||||
] = useState<SavedQueryObject | null>(null);
|
||||
const [importingSavedQuery, showImportModal] = useState<boolean>(false);
|
||||
const [passwordFields, setPasswordFields] = useState<string[]>([]);
|
||||
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
||||
|
||||
const openSavedQueryImportModal = () => {
|
||||
showImportModal(true);
|
||||
@@ -238,6 +240,16 @@ function SavedQueryList({
|
||||
);
|
||||
};
|
||||
|
||||
const handleBulkSavedQueryExport = (
|
||||
savedQueriesToExport: SavedQueryObject[],
|
||||
) => {
|
||||
const ids = savedQueriesToExport.map(({ id }) => id);
|
||||
handleResourceExport('saved_query', ids, () => {
|
||||
setPreparingExport(false);
|
||||
});
|
||||
setPreparingExport(true);
|
||||
};
|
||||
|
||||
const handleBulkQueryDelete = (queriesToDelete: SavedQueryObject[]) => {
|
||||
SupersetClient.delete({
|
||||
endpoint: `/api/v1/saved_query/?q=${rison.encode(
|
||||
@@ -542,6 +554,7 @@ function SavedQueryList({
|
||||
passwordFields={passwordFields}
|
||||
setPasswordFields={setPasswordFields}
|
||||
/>
|
||||
{preparingExport && <Loading />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import rison from 'rison';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { FetchDataConfig } from 'src/components/ListView';
|
||||
import SupersetText from 'src/utils/textUtils';
|
||||
import { Dashboard, Filters, SavedQueryObject } from './types';
|
||||
import { Dashboard, Filters } from './types';
|
||||
|
||||
const createFetchResourceMethod = (method: string) => (
|
||||
resource: string,
|
||||
@@ -219,32 +219,6 @@ export function handleChartDelete(
|
||||
);
|
||||
}
|
||||
|
||||
export function handleBulkChartExport(chartsToExport: Chart[]) {
|
||||
return window.location.assign(
|
||||
`/api/v1/chart/export/?q=${rison.encode(
|
||||
chartsToExport.map(({ id }) => id),
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function handleBulkDashboardExport(dashboardsToExport: Dashboard[]) {
|
||||
return window.location.assign(
|
||||
`/api/v1/dashboard/export/?q=${rison.encode(
|
||||
dashboardsToExport.map(({ id }) => id),
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function handleBulkSavedQueryExport(
|
||||
savedQueriesToExport: SavedQueryObject[],
|
||||
) {
|
||||
return window.location.assign(
|
||||
`/api/v1/saved_query/export/?q=${rison.encode(
|
||||
savedQueriesToExport.map(({ id }) => id),
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function handleDashboardDelete(
|
||||
{ id, dashboard_title: dashboardTitle }: Dashboard,
|
||||
refreshData: (config?: FetchDataConfig | null) => void,
|
||||
|
||||
@@ -33,6 +33,7 @@ import PropertiesModal from 'src/explore/components/PropertiesModal';
|
||||
import { User } from 'src/types/bootstrapTypes';
|
||||
import ChartCard from 'src/views/CRUD/chart/ChartCard';
|
||||
import Chart from 'src/types/Chart';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
import Loading from 'src/components/Loading';
|
||||
import ErrorBoundary from 'src/components/ErrorBoundary';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
@@ -90,6 +91,7 @@ function ChartTable({
|
||||
} = useChartEditModal(setCharts, charts);
|
||||
|
||||
const [chartFilter, setChartFilter] = useState('Mine');
|
||||
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const filter = getFromLocalStorage('chart', null);
|
||||
@@ -98,6 +100,14 @@ function ChartTable({
|
||||
} else setChartFilter(filter.tab);
|
||||
}, []);
|
||||
|
||||
const handleBulkChartExport = (chartsToExport: Chart[]) => {
|
||||
const ids = chartsToExport.map(({ id }) => id);
|
||||
handleResourceExport('chart', ids, () => {
|
||||
setPreparingExport(false);
|
||||
});
|
||||
setPreparingExport(true);
|
||||
};
|
||||
|
||||
const getFilters = (filterName: string) => {
|
||||
const filters = [];
|
||||
|
||||
@@ -208,12 +218,14 @@ function ChartTable({
|
||||
addSuccessToast={addSuccessToast}
|
||||
favoriteStatus={favoriteStatus[e.id]}
|
||||
saveFavoriteStatus={saveFavoriteStatus}
|
||||
handleBulkChartExport={handleBulkChartExport}
|
||||
/>
|
||||
))}
|
||||
</CardContainer>
|
||||
) : (
|
||||
<EmptyState tableName="CHARTS" tab={chartFilter} />
|
||||
)}
|
||||
{preparingExport && <Loading />}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import React, { useState, useMemo, useEffect } from 'react';
|
||||
import { SupersetClient, t } from '@superset-ui/core';
|
||||
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
|
||||
import { Dashboard, DashboardTableProps } from 'src/views/CRUD/types';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
setInLocalStorage,
|
||||
@@ -72,6 +73,7 @@ function DashboardTable({
|
||||
);
|
||||
const [editModal, setEditModal] = useState<Dashboard>();
|
||||
const [dashboardFilter, setDashboardFilter] = useState('Mine');
|
||||
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const filter = getFromLocalStorage('dashboard', null);
|
||||
@@ -80,6 +82,14 @@ function DashboardTable({
|
||||
} else setDashboardFilter(filter.tab);
|
||||
}, []);
|
||||
|
||||
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}`,
|
||||
@@ -221,6 +231,7 @@ function DashboardTable({
|
||||
}
|
||||
saveFavoriteStatus={saveFavoriteStatus}
|
||||
favoriteStatus={favoriteStatus[e.id]}
|
||||
handleBulkDashboardExport={handleBulkDashboardExport}
|
||||
/>
|
||||
))}
|
||||
</CardContainer>
|
||||
@@ -228,6 +239,7 @@ function DashboardTable({
|
||||
{dashboards.length === 0 && (
|
||||
<EmptyState tableName="DASHBOARDS" tab={dashboardFilter} />
|
||||
)}
|
||||
{preparingExport && <Loading />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -903,6 +903,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
||||
500:
|
||||
$ref: '#/components/responses/500'
|
||||
"""
|
||||
token = request.args.get("token")
|
||||
requested_ids = kwargs["rison"]
|
||||
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
||||
root = f"chart_export_{timestamp}"
|
||||
@@ -918,12 +919,15 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
||||
return self.response_404()
|
||||
buf.seek(0)
|
||||
|
||||
return send_file(
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
attachment_filename=filename,
|
||||
)
|
||||
if token:
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
|
||||
@expose("/favorite_status/", methods=["GET"])
|
||||
@protect()
|
||||
|
||||
@@ -706,6 +706,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
||||
requested_ids = kwargs["rison"]
|
||||
|
||||
if is_feature_enabled("VERSIONED_EXPORT"):
|
||||
token = request.args.get("token")
|
||||
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
||||
root = f"dashboard_export_{timestamp}"
|
||||
filename = f"{root}.zip"
|
||||
@@ -722,12 +723,15 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
||||
return self.response_404()
|
||||
buf.seek(0)
|
||||
|
||||
return send_file(
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
attachment_filename=filename,
|
||||
)
|
||||
if token:
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
|
||||
query = self.datamodel.session.query(Dashboard).filter(
|
||||
Dashboard.id.in_(requested_ids)
|
||||
|
||||
@@ -722,6 +722,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
||||
500:
|
||||
$ref: '#/components/responses/500'
|
||||
"""
|
||||
token = request.args.get("token")
|
||||
requested_ids = kwargs["rison"]
|
||||
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
||||
root = f"database_export_{timestamp}"
|
||||
@@ -739,12 +740,15 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
||||
return self.response_404()
|
||||
buf.seek(0)
|
||||
|
||||
return send_file(
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
attachment_filename=filename,
|
||||
)
|
||||
if token:
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
|
||||
@expose("/import/", methods=["POST"])
|
||||
@protect()
|
||||
|
||||
@@ -432,6 +432,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
|
||||
requested_ids = kwargs["rison"]
|
||||
|
||||
if is_feature_enabled("VERSIONED_EXPORT"):
|
||||
token = request.args.get("token")
|
||||
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
||||
root = f"dataset_export_{timestamp}"
|
||||
filename = f"{root}.zip"
|
||||
@@ -448,12 +449,15 @@ class DatasetRestApi(BaseSupersetModelRestApi):
|
||||
return self.response_404()
|
||||
buf.seek(0)
|
||||
|
||||
return send_file(
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
attachment_filename=filename,
|
||||
)
|
||||
if token:
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
|
||||
query = self.datamodel.session.query(SqlaTable).filter(
|
||||
SqlaTable.id.in_(requested_ids)
|
||||
|
||||
@@ -237,6 +237,7 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
|
||||
500:
|
||||
$ref: '#/components/responses/500'
|
||||
"""
|
||||
token = request.args.get("token")
|
||||
requested_ids = kwargs["rison"]
|
||||
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
||||
root = f"saved_query_export_{timestamp}"
|
||||
@@ -254,12 +255,15 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
|
||||
return self.response_404()
|
||||
buf.seek(0)
|
||||
|
||||
return send_file(
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
attachment_filename=filename,
|
||||
)
|
||||
if token:
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
|
||||
@expose("/import/", methods=["POST"])
|
||||
@protect()
|
||||
|
||||
Reference in New Issue
Block a user