mirror of https://github.com/apache/superset.git
Front end for VERSIONED_EXPORT (#11559)
This commit is contained in:
parent
128ddfabb6
commit
d999802795
|
@ -31,6 +31,7 @@ export enum FeatureFlag {
|
|||
ENABLE_REACT_CRUD_VIEWS = 'ENABLE_REACT_CRUD_VIEWS',
|
||||
DISPLAY_MARKDOWN_HTML = 'DISPLAY_MARKDOWN_HTML',
|
||||
ESCAPE_MARKDOWN_HTML = 'ESCAPE_MARKDOWN_HTML',
|
||||
VERSIONED_EXPORT = 'VERSIONED_EXPORT',
|
||||
}
|
||||
|
||||
export type FeatureFlagMap = {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
import Icon from 'src/components/Icon';
|
||||
import Chart from 'src/types/Chart';
|
||||
|
@ -27,7 +28,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 } from '../utils';
|
||||
import { handleBulkChartExport, handleChartDelete } from '../utils';
|
||||
|
||||
interface ChartCardProps {
|
||||
chart: Chart;
|
||||
|
@ -56,6 +57,8 @@ export default function ChartCard({
|
|||
}: ChartCardProps) {
|
||||
const canEdit = hasPerm('can_edit');
|
||||
const canDelete = hasPerm('can_delete');
|
||||
const canExport =
|
||||
hasPerm('can_mulexport') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT);
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
|
@ -92,6 +95,15 @@ export default function ChartCard({
|
|||
</ConfirmStatusChange>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{canExport && (
|
||||
<Menu.Item
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => handleBulkChartExport([chart])}
|
||||
>
|
||||
<ListViewCard.MenuIcon name="share" /> {t('Export')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{canEdit && (
|
||||
<Menu.Item
|
||||
data-test="chart-list-edit-option"
|
||||
|
|
|
@ -24,6 +24,7 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
|||
import {
|
||||
createFetchRelated,
|
||||
createErrorHandler,
|
||||
handleBulkChartExport,
|
||||
handleChartDelete,
|
||||
} from 'src/views/CRUD/utils';
|
||||
import {
|
||||
|
@ -123,6 +124,8 @@ function ChartList(props: ChartListProps) {
|
|||
const canCreate = hasPerm('can_add');
|
||||
const canEdit = hasPerm('can_edit');
|
||||
const canDelete = hasPerm('can_delete');
|
||||
const canExport =
|
||||
hasPerm('can_mulexport') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT);
|
||||
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||
|
||||
function handleBulkChartDelete(chartsToDelete: Chart[]) {
|
||||
|
@ -245,6 +248,10 @@ function ChartList(props: ChartListProps) {
|
|||
refreshData,
|
||||
);
|
||||
const openEditModal = () => openChartEditModal(original);
|
||||
const handleExport = () => handleBulkChartExport([original]);
|
||||
if (!canEdit && !canDelete && !canExport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="actions">
|
||||
|
@ -277,6 +284,22 @@ function ChartList(props: ChartListProps) {
|
|||
)}
|
||||
</ConfirmStatusChange>
|
||||
)}
|
||||
{canExport && (
|
||||
<TooltipWrapper
|
||||
label="export-action"
|
||||
tooltip={t('Export')}
|
||||
placement="bottom"
|
||||
>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={handleExport}
|
||||
>
|
||||
<Icon name="share" />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
{canEdit && (
|
||||
<TooltipWrapper
|
||||
label="edit-action"
|
||||
|
@ -302,7 +325,7 @@ function ChartList(props: ChartListProps) {
|
|||
hidden: !canEdit && !canDelete,
|
||||
},
|
||||
],
|
||||
[canEdit, canDelete, favoriteStatus],
|
||||
[canEdit, canDelete, canExport, favoriteStatus],
|
||||
);
|
||||
|
||||
const filters: Filters = [
|
||||
|
@ -434,7 +457,7 @@ function ChartList(props: ChartListProps) {
|
|||
);
|
||||
}
|
||||
const subMenuButtons: SubMenuProps['buttons'] = [];
|
||||
if (canDelete) {
|
||||
if (canDelete || canExport) {
|
||||
subMenuButtons.push({
|
||||
name: t('Bulk Select'),
|
||||
buttonStyle: 'secondary',
|
||||
|
@ -471,17 +494,23 @@ function ChartList(props: ChartListProps) {
|
|||
onConfirm={handleBulkChartDelete}
|
||||
>
|
||||
{confirmDelete => {
|
||||
const bulkActions: ListViewProps['bulkActions'] = canDelete
|
||||
? [
|
||||
{
|
||||
key: 'delete',
|
||||
name: t('Delete'),
|
||||
onSelect: confirmDelete,
|
||||
type: 'danger',
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const bulkActions: ListViewProps['bulkActions'] = [];
|
||||
if (canDelete) {
|
||||
bulkActions.push({
|
||||
key: 'delete',
|
||||
name: t('Delete'),
|
||||
type: 'danger',
|
||||
onSelect: confirmDelete,
|
||||
});
|
||||
}
|
||||
if (canExport) {
|
||||
bulkActions.push({
|
||||
key: 'export',
|
||||
name: t('Export'),
|
||||
type: 'primary',
|
||||
onSelect: handleBulkChartExport,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ListView<Chart>
|
||||
bulkActions={bulkActions}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
import { SupersetClient, t, styled } from '@superset-ui/core';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import rison from 'rison';
|
||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||
import { useListViewResource } from 'src/views/CRUD/hooks';
|
||||
import { createErrorHandler } from 'src/views/CRUD/utils';
|
||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||
|
@ -119,6 +121,8 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
|||
const canCreate = hasPerm('can_add');
|
||||
const canEdit = hasPerm('can_edit');
|
||||
const canDelete = hasPerm('can_delete');
|
||||
const canExport =
|
||||
hasPerm('can_mulexport') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT);
|
||||
|
||||
const menuData: SubMenuProps = {
|
||||
activeChild: 'Databases',
|
||||
|
@ -143,6 +147,12 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
|||
];
|
||||
}
|
||||
|
||||
function handleDatabaseExport(database: DatabaseObject) {
|
||||
return window.location.assign(
|
||||
`/api/v1/database/export/?q=${rison.encode([database.id])}`,
|
||||
);
|
||||
}
|
||||
|
||||
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
|
@ -238,25 +248,12 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
|||
Cell: ({ row: { original } }: any) => {
|
||||
const handleEdit = () => handleDatabaseEdit(original);
|
||||
const handleDelete = () => openDatabaseDeleteModal(original);
|
||||
|
||||
const handleExport = () => handleDatabaseExport(original);
|
||||
if (!canEdit && !canDelete && !canExport) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span className="actions">
|
||||
{canEdit && (
|
||||
<TooltipWrapper
|
||||
label="edit-action"
|
||||
tooltip={t('Edit')}
|
||||
placement="bottom"
|
||||
>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={handleEdit}
|
||||
>
|
||||
<Icon name="edit-alt" />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
{canDelete && (
|
||||
<span
|
||||
role="button"
|
||||
|
@ -274,6 +271,38 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
|||
</TooltipWrapper>
|
||||
</span>
|
||||
)}
|
||||
{canExport && (
|
||||
<TooltipWrapper
|
||||
label="export-action"
|
||||
tooltip={t('Export')}
|
||||
placement="bottom"
|
||||
>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={handleExport}
|
||||
>
|
||||
<Icon name="share" />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
{canEdit && (
|
||||
<TooltipWrapper
|
||||
label="edit-action"
|
||||
tooltip={t('Edit')}
|
||||
placement="bottom"
|
||||
>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={handleEdit}
|
||||
>
|
||||
<Icon name="edit-alt" />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
@ -283,7 +312,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
|||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[canDelete, canEdit],
|
||||
[canDelete, canEdit, canExport],
|
||||
);
|
||||
|
||||
const filters: Filters = useMemo(
|
||||
|
|
|
@ -113,6 +113,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||
const canEdit = hasPerm('can_edit');
|
||||
const canDelete = hasPerm('can_delete');
|
||||
const canCreate = hasPerm('can_add');
|
||||
const canExport = hasPerm('can_mulexport');
|
||||
|
||||
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||
|
||||
|
@ -282,7 +283,10 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||
Cell: ({ row: { original } }: any) => {
|
||||
const handleEdit = () => openDatasetEditModal(original);
|
||||
const handleDelete = () => openDatasetDeleteModal(original);
|
||||
|
||||
const handleExport = () => handleBulkDatasetExport([original]);
|
||||
if (!canEdit && !canDelete && !canExport) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span className="actions">
|
||||
{canDelete && (
|
||||
|
@ -301,7 +305,22 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||
</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
|
||||
{canExport && (
|
||||
<TooltipWrapper
|
||||
label="export-action"
|
||||
tooltip={t('Export')}
|
||||
placement="bottom"
|
||||
>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={handleExport}
|
||||
>
|
||||
<Icon name="share" />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
{canEdit && (
|
||||
<TooltipWrapper
|
||||
label="edit-action"
|
||||
|
@ -327,7 +346,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[canEdit, canDelete, openDatasetEditModal],
|
||||
[canEdit, canDelete, canExport, openDatasetEditModal],
|
||||
);
|
||||
|
||||
const filterTypes: Filters = useMemo(
|
||||
|
@ -408,7 +427,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||
|
||||
const buttonArr: Array<ButtonProps> = [];
|
||||
|
||||
if (canDelete) {
|
||||
if (canDelete || canExport) {
|
||||
buttonArr.push({
|
||||
name: t('Bulk Select'),
|
||||
onClick: toggleBulkSelect,
|
||||
|
@ -473,6 +492,14 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const handleBulkDatasetExport = (datasetsToExport: Dataset[]) => {
|
||||
return window.location.assign(
|
||||
`/api/v1/dataset/export/?q=${rison.encode(
|
||||
datasetsToExport.map(({ id }) => id),
|
||||
)}`,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SubMenu {...menuData} />
|
||||
|
@ -515,17 +542,23 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||
onConfirm={handleBulkDatasetDelete}
|
||||
>
|
||||
{confirmDelete => {
|
||||
const bulkActions: ListViewProps['bulkActions'] = canDelete
|
||||
? [
|
||||
{
|
||||
key: 'delete',
|
||||
name: t('Delete'),
|
||||
onSelect: confirmDelete,
|
||||
type: 'danger',
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const bulkActions: ListViewProps['bulkActions'] = [];
|
||||
if (canDelete) {
|
||||
bulkActions.push({
|
||||
key: 'delete',
|
||||
name: t('Delete'),
|
||||
onSelect: confirmDelete,
|
||||
type: 'danger',
|
||||
});
|
||||
}
|
||||
if (canExport) {
|
||||
bulkActions.push({
|
||||
key: 'export',
|
||||
name: t('Export'),
|
||||
type: 'primary',
|
||||
onSelect: handleBulkDatasetExport,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ListView<Dataset>
|
||||
className="dataset-list-view"
|
||||
|
|
|
@ -183,6 +183,14 @@ 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(
|
||||
|
|
|
@ -323,6 +323,7 @@ DEFAULT_FEATURE_FLAGS: Dict[str, bool] = {
|
|||
# When True, this escapes HTML (rather than rendering it) in Markdown components
|
||||
"ESCAPE_MARKDOWN_HTML": False,
|
||||
"SIP_34_ANNOTATIONS_UI": False,
|
||||
"VERSIONED_EXPORT": False,
|
||||
}
|
||||
|
||||
# Set the default view to card/grid view if thumbnail support is enabled.
|
||||
|
|
|
@ -50,7 +50,7 @@ stats_logger = config["STATS_LOGGER"]
|
|||
|
||||
def sqlalchemy_uri_form_validator(_: _, field: StringField) -> None:
|
||||
"""
|
||||
Check if user has submitted a valid SQLAlchemy URI
|
||||
Check if user has submitted a valid SQLAlchemy URI
|
||||
"""
|
||||
|
||||
sqlalchemy_uri_validator(field.data, exception=ValidationError)
|
||||
|
@ -58,7 +58,7 @@ def sqlalchemy_uri_form_validator(_: _, field: StringField) -> None:
|
|||
|
||||
def certificate_form_validator(_: _, field: StringField) -> None:
|
||||
"""
|
||||
Check if user has submitted a valid SSL certificate
|
||||
Check if user has submitted a valid SSL certificate
|
||||
"""
|
||||
if field.data:
|
||||
try:
|
||||
|
|
Loading…
Reference in New Issue