Front end for VERSIONED_EXPORT (#11559)

This commit is contained in:
Beto Dealmeida 2020-11-04 14:22:12 -08:00 committed by GitHub
parent 128ddfabb6
commit d999802795
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 162 additions and 49 deletions

View File

@ -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 = {

View File

@ -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"

View File

@ -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}

View File

@ -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(

View File

@ -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"

View File

@ -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(

View File

@ -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.

View File

@ -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: