feat: update delete modal for dataset (#10258)

* update delete modal for dataset

* update datasetList to use hooks

* fix typo on dataset delete modal
This commit is contained in:
Lily Kuang 2020-07-10 14:23:17 -07:00 committed by GitHub
parent 4d179622fa
commit 80b06f6827
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 448 additions and 357 deletions

View File

@ -18,8 +18,10 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { Modal, Button } from 'react-bootstrap';
import { Button } from 'react-bootstrap';
import { supersetTheme, ThemeProvider } from '@superset-ui/style';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import Modal from 'src/components/Modal';
describe('ConfirmStatusChange', () => {
const mockedProps = {
@ -35,6 +37,10 @@ describe('ConfirmStatusChange', () => {
</>
)}
</ConfirmStatusChange>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
},
);
it('opens a confirm modal', () => {

View File

@ -16,63 +16,55 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@superset-ui/translation';
import * as React from 'react';
// @ts-ignore
import { Button, Modal } from 'react-bootstrap';
import React, { useState } from 'react';
import DeleteModal from 'src/components/DeleteModal';
type Callback = (...args: any[]) => void;
interface Props {
title: string | React.ReactNode;
description: string | React.ReactNode;
interface ConfirmStatusChangeProps {
title: React.ReactNode;
description: React.ReactNode;
onConfirm: Callback;
children: (showConfirm: Callback) => React.ReactNode;
}
interface State {
callbackArgs: any[];
open: boolean;
}
export default class ConfirmStatusChange extends React.Component<Props, State> {
public state = {
callbackArgs: [],
open: false,
};
export default function ConfirmStatusChange({
title,
description,
onConfirm,
children,
}: ConfirmStatusChangeProps) {
const [open, setOpen] = useState(false);
const [currentCallbackArgs, setCurrentCallbackArgs] = useState<any[]>([]);
public showConfirm = (...callbackArgs: any[]) => {
const showConfirm = (...callbackArgs: any[]) => {
// check if any args are DOM events, if so, call persist
callbackArgs.forEach(
arg => arg && typeof arg.persist === 'function' && arg.persist(),
);
this.setState({
callbackArgs,
open: true,
});
setOpen(true);
setCurrentCallbackArgs(callbackArgs);
};
public hide = () => this.setState({ open: false, callbackArgs: [] });
public confirm = () => {
this.props.onConfirm(...this.state.callbackArgs);
this.hide();
const hide = () => {
setOpen(false);
setCurrentCallbackArgs([]);
};
public render() {
return (
<>
{this.props.children && this.props.children(this.showConfirm)}
<Modal show={this.state.open} onHide={this.hide}>
<Modal.Header closeButton>{this.props.title}</Modal.Header>
<Modal.Body>{this.props.description}</Modal.Body>
<Modal.Footer>
<Button onClick={this.hide}>{t('Cancel')}</Button>
<Button bsStyle="danger" onClick={this.confirm}>
{t('OK')}
</Button>
</Modal.Footer>
</Modal>
</>
);
}
const confirm = () => {
onConfirm(...currentCallbackArgs);
hide();
};
return (
<>
{children && children(showConfirm)}
<DeleteModal
description={description}
onConfirm={confirm}
onHide={hide}
open={open}
title={title}
/>
</>
);
}

View File

@ -0,0 +1,80 @@
/**
* 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 { t } from '@superset-ui/translation';
import React, { useState } from 'react';
import styled from '@superset-ui/style';
import { FormGroup, FormControl } from 'react-bootstrap';
import Modal from 'src/components/Modal';
const StyleFormGroup = styled(FormGroup)`
padding-top: 8px;
width: 50%;
label {
color: ${({ theme }) => theme.colors.grayscale.light1};
text-transform: uppercase;
}
`;
const DescriptionContainer = styled.div`
line-height: 40px;
padding-top: 16px;
`;
interface DeleteModalProps {
description: React.ReactNode;
onConfirm: () => void;
onHide: () => void;
open: boolean;
title: React.ReactNode;
}
export default function DeleteModal({
description,
onConfirm,
onHide,
open,
title,
}: DeleteModalProps) {
const [disableChange, setDisableChange] = useState(true);
return (
<Modal
disablePrimaryButton={disableChange}
onHide={onHide}
onHandledPrimaryAction={onConfirm}
primaryButtonName={t('delete')}
primaryButtonType="danger"
show={open}
title={title}
>
<DescriptionContainer>{description}</DescriptionContainer>
<StyleFormGroup>
<label htmlFor="delete">{t('type delete to confirm')}</label>
<FormControl
id="delete"
type="text"
// @ts-ignore
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setDisableChange(event.target.value !== 'DELETE')
}
/>
</StyleFormGroup>
</Modal>
);
}

View File

@ -20,13 +20,15 @@ import React from 'react';
import styled from '@superset-ui/style';
import { Modal as BaseModal } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import Button from './Button';
import Button from '../views/datasetList/Button';
interface ModalProps {
children: React.ReactNode;
disableSave: boolean;
disablePrimaryButton?: boolean;
onHide: () => void;
onSave: () => void;
onHandledPrimaryAction: () => void;
primaryButtonName: string;
primaryButtonType?: 'primary' | 'danger';
show: boolean;
title: React.ReactNode;
}
@ -45,8 +47,7 @@ const StyledModal = styled(BaseModal)`
}
.modal-body {
padding: 18px 0 340px 18px;
width: 65%;
padding: 18px;
}
.modal-footer {
@ -66,9 +67,11 @@ const Title = styled.div`
export default function Modal({
children,
disableSave,
disablePrimaryButton = false,
onHandledPrimaryAction,
onHide,
onSave,
primaryButtonName,
primaryButtonType = 'primary',
show,
title,
}: ModalProps) {
@ -83,8 +86,12 @@ export default function Modal({
<BaseModal.Footer>
<span className="float-right">
<Button onClick={onHide}>{t('Cancel')}</Button>
<Button bsStyle="primary" disabled={disableSave} onClick={onSave}>
{t('Add')}
<Button
bsStyle={primaryButtonType}
disabled={disablePrimaryButton}
onClick={onHandledPrimaryAction}
>
{primaryButtonName}
</Button>
</span>
</BaseModal.Footer>

View File

@ -25,7 +25,7 @@ interface ModalProps {
disabled?: boolean;
onClick: () => void;
padding?: number;
bsStyle?: 'default' | 'primary';
bsStyle?: 'default' | 'primary' | 'danger';
width?: number;
}
@ -51,6 +51,11 @@ const StyledButton = styled(BaseButton)`
background-color: ${({ theme }) => theme.colors.primary.base};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
&.btn-danger,
&.btn-danger:hover {
background-color: ${({ theme }) => theme.colors.error.base};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
`;
export default function Modal({

View File

@ -19,13 +19,18 @@
import { SupersetClient } from '@superset-ui/connection';
import { t } from '@superset-ui/translation';
import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
import React, {
FunctionComponent,
useCallback,
useEffect,
useState,
} from 'react';
import rison from 'rison';
// @ts-ignore
import { Panel } from 'react-bootstrap';
import { SHORT_DATE, SHORT_TIME } from 'src/utils/common';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import DeleteModal from 'src/components/DeleteModal';
import ListView from 'src/components/ListView/ListView';
import SubMenu from 'src/components/Menu/SubMenu';
import AvatarIcon from 'src/components/AvatarIcon';
@ -41,29 +46,17 @@ import Icon from 'src/components/Icon';
const PAGE_SIZE = 25;
type Owner = {
id: string;
first_name: string;
id: string;
last_name: string;
username: string;
};
interface Props {
interface DatasetListProps {
addDangerToast: (msg: string) => void;
addSuccessToast: (msg: string) => void;
}
interface State {
datasets: any[];
datasetCount: number;
loading: boolean;
filterOperators: FilterOperatorMap;
filters: Filters;
owners: Array<{ text: string; value: number }>;
databases: Array<{ text: string; value: number }>;
permissions: string[];
lastFetchDataConfig: FetchDataConfig | null;
}
interface Dataset {
changed_by: string;
changed_by_name: string;
@ -77,24 +70,78 @@ interface Dataset {
table_name: string;
}
class DatasetList extends React.PureComponent<Props, State> {
static propTypes = {
addDangerToast: PropTypes.func.isRequired,
const DatasetList: FunctionComponent<DatasetListProps> = ({
addDangerToast,
addSuccessToast,
}) => {
const [databases, setDatabases] = useState<{ text: string; value: number }[]>(
[],
);
const [datasetCount, setDatasetCount] = useState(0);
const [datasetCurrentlyDeleting, setDatasetCurrentlyDeleting] = useState<
(Dataset & { chart_count: number; dashboard_count: number }) | null
>(null);
const [datasets, setDatasets] = useState<any[]>([]);
const [currentFilters, setCurrentFilters] = useState<Filters>([]);
const [
lastFetchDataConfig,
setLastFetchDataConfig,
] = useState<FetchDataConfig | null>(null);
const [loading, setLoading] = useState(true);
const [currentOwners, setCurrentOwners] = useState<
{ text: string; value: number }[]
>([]);
const [permissions, setPermissions] = useState<string[]>([]);
const updateFilters = (filterOperators: FilterOperatorMap) => {
const convertFilter = ({
name: label,
operator,
}: {
name: string;
operator: string;
}) => ({ label, value: operator });
setCurrentFilters([
{
Header: 'Database',
id: 'database',
input: 'select',
operators: filterOperators.database.map(convertFilter),
selects: databases.map(({ text: label, value }) => ({
label,
value,
})),
},
{
Header: 'Schema',
id: 'schema',
operators: filterOperators.schema.map(convertFilter),
},
{
Header: 'Table Name',
id: 'table_name',
operators: filterOperators.table_name.map(convertFilter),
},
{
Header: 'Owners',
id: 'owners',
input: 'select',
operators: filterOperators.owners.map(convertFilter),
selects: currentOwners.map(({ text: label, value }) => ({
label,
value,
})),
},
{
Header: 'SQL Lab View',
id: 'is_sqllab_view',
input: 'checkbox',
operators: filterOperators.is_sqllab_view.map(convertFilter),
},
]);
};
state: State = {
datasetCount: 0,
datasets: [],
filterOperators: {},
filters: [],
lastFetchDataConfig: null,
loading: true,
owners: [],
databases: [],
permissions: [],
};
componentDidMount() {
const fetchDataset = () => {
Promise.all([
SupersetClient.get({
endpoint: `/api/v1/dataset/_info`,
@ -111,20 +158,13 @@ class DatasetList extends React.PureComponent<Props, State> {
{ json: ownersJson = {} },
{ json: databasesJson = {} },
]) => {
this.setState(
{
filterOperators: infoJson.filters,
owners: ownersJson.result,
databases: databasesJson.result,
permissions: infoJson.permissions,
},
this.updateFilters,
);
setCurrentOwners(ownersJson.result);
setDatabases(databasesJson.result);
setPermissions(infoJson.permissions);
updateFilters(infoJson.filters);
},
([e1, e2]) => {
this.props.addDangerToast(
t('An error occurred while fetching datasets'),
);
addDangerToast(t('An error occurred while fetching datasets'));
if (e1) {
console.error(e1);
}
@ -133,23 +173,48 @@ class DatasetList extends React.PureComponent<Props, State> {
}
},
);
}
};
get canEdit() {
return this.hasPerm('can_edit');
}
useEffect(() => {
fetchDataset();
}, []);
get canDelete() {
return this.hasPerm('can_delete');
}
const hasPerm = (perm: string) => {
if (!permissions.length) {
return false;
}
get canCreate() {
return this.hasPerm('can_add');
}
return Boolean(permissions.find(p => p === perm));
};
initialSort = [{ id: 'changed_on', desc: true }];
const canEdit = () => hasPerm('can_edit');
const canDelete = () => hasPerm('can_delete');
const canCreate = () => hasPerm('can_add');
columns = [
const initialSort = [{ id: 'changed_on', desc: true }];
const handleDatasetEdit = ({ id }: { id: number }) => {
window.location.assign(`/tablemodelview/edit/${id}`);
};
const openDatasetDeleteModal = (dataset: Dataset) =>
SupersetClient.get({
endpoint: `/api/v1/dataset/${dataset.id}/related_objects`,
})
.then(({ json = {} }) => {
setDatasetCurrentlyDeleting({
...dataset,
chart_count: json.charts.count,
dashboard_count: json.dashboards.count,
});
})
.catch(() => {
addDangerToast(
t('An error occurred while fetching dataset related data'),
);
});
const columns = [
{
Cell: ({
row: {
@ -285,9 +350,9 @@ class DatasetList extends React.PureComponent<Props, State> {
},
{
Cell: ({ row: { state, original } }: any) => {
const handleDelete = () => this.handleDatasetDelete(original);
const handleEdit = () => this.handleDatasetEdit(original);
if (!this.canEdit && !this.canDelete) {
const handleEdit = () => handleDatasetEdit(original);
const handleDelete = () => openDatasetDeleteModal(original);
if (!canEdit() && !canDelete()) {
return null;
}
return (
@ -308,36 +373,24 @@ class DatasetList extends React.PureComponent<Props, State> {
<Icon name="compass" />
</a>
</TooltipWrapper>
{this.canDelete && (
<ConfirmStatusChange
title={t('Please Confirm')}
description={
<>
{t('Are you sure you want to delete ')}{' '}
<b>{original.table_name}</b>?
</>
}
onConfirm={handleDelete}
{canDelete && (
<TooltipWrapper
label="delete-action"
tooltip={t('Delete')}
placement="bottom"
>
{confirmDelete => (
<TooltipWrapper
label="delete-action"
tooltip={t('Delete')}
placement="bottom"
>
<span
role="button"
tabIndex={0}
className="action-button"
onClick={confirmDelete}
>
<Icon name="trash" />
</span>
</TooltipWrapper>
)}
</ConfirmStatusChange>
<span
role="button"
tabIndex={0}
className="action-button"
onClick={handleDelete}
>
<Icon name="trash" />
</span>
</TooltipWrapper>
)}
{this.canEdit && (
{canEdit() && (
<TooltipWrapper
label="edit-action"
tooltip={t('Edit')}
@ -362,7 +415,7 @@ class DatasetList extends React.PureComponent<Props, State> {
},
];
menu = {
const menu = {
name: t('Data'),
createButton: {
name: t('Dataset'),
@ -383,194 +436,147 @@ class DatasetList extends React.PureComponent<Props, State> {
],
};
hasPerm = (perm: string) => {
if (!this.state.permissions.length) {
return false;
}
return Boolean(this.state.permissions.find(p => p === perm));
const closeDatasetDeleteModal = () => {
setDatasetCurrentlyDeleting(null);
};
handleDatasetEdit = ({ id }: { id: number }) => {
window.location.assign(`/tablemodelview/edit/${id}`);
};
const fetchData = useCallback(
({ pageIndex, pageSize, sortBy, filters }: FetchDataConfig) => {
// set loading state, cache the last config for fetching data in this component.
setLoading(true);
setLastFetchDataConfig({
filters,
pageIndex,
pageSize,
sortBy,
});
const filterExps = filters.map(({ id: col, operator: opr, value }) => ({
col,
opr,
value,
}));
handleDatasetDelete = ({ id, table_name: tableName }: Dataset) =>
const queryParams = rison.encode({
order_column: sortBy[0].id,
order_direction: sortBy[0].desc ? 'desc' : 'asc',
page: pageIndex,
page_size: pageSize,
...(filterExps.length ? { filters: filterExps } : {}),
});
return SupersetClient.get({
endpoint: `/api/v1/dataset/?q=${queryParams}`,
})
.then(({ json }) => {
setLoading(false);
setDatasets(json.result);
setDatasetCount(json.count);
})
.catch(() => {
addDangerToast(t('An error occurred while fetching datasets'));
setLoading(false);
});
},
[],
);
const handleDatasetDelete = ({ id, table_name: tableName }: Dataset) => {
SupersetClient.delete({
endpoint: `/api/v1/dataset/${id}`,
}).then(
() => {
const { lastFetchDataConfig } = this.state;
if (lastFetchDataConfig) {
this.fetchData(lastFetchDataConfig);
fetchData(lastFetchDataConfig);
}
this.props.addSuccessToast(t('Deleted: %s', tableName));
setDatasetCurrentlyDeleting(null);
addSuccessToast(t('Deleted: %s', tableName));
},
(err: any) => {
console.error(err);
this.props.addDangerToast(
t('There was an issue deleting %s', tableName),
);
addDangerToast(t('There was an issue deleting %s', tableName));
},
);
};
handleBulkDatasetDelete = (datasets: Dataset[]) => {
const handleBulkDatasetDelete = () => {
SupersetClient.delete({
endpoint: `/api/v1/dataset/?q=${rison.encode(
datasets.map(({ id }) => id),
)}`,
}).then(
({ json = {} }) => {
const { lastFetchDataConfig } = this.state;
if (lastFetchDataConfig) {
this.fetchData(lastFetchDataConfig);
fetchData(lastFetchDataConfig);
}
this.props.addSuccessToast(json.message);
addSuccessToast(json.message);
},
(err: any) => {
console.error(err);
this.props.addDangerToast(
t('There was an issue deleting the selected datasets'),
);
addDangerToast(t('There was an issue deleting the selected datasets'));
},
);
};
fetchData = ({ pageIndex, pageSize, sortBy, filters }: FetchDataConfig) => {
// set loading state, cache the last config for fetching data in this component.
this.setState({
lastFetchDataConfig: {
filters,
pageIndex,
pageSize,
sortBy,
},
loading: true,
});
const filterExps = filters.map(({ id: col, operator: opr, value }) => ({
col,
opr,
value,
}));
const queryParams = rison.encode({
order_column: sortBy[0].id,
order_direction: sortBy[0].desc ? 'desc' : 'asc',
page: pageIndex,
page_size: pageSize,
...(filterExps.length ? { filters: filterExps } : {}),
});
return SupersetClient.get({
endpoint: `/api/v1/dataset/?q=${queryParams}`,
})
.then(({ json = {} }) => {
this.setState({ datasets: json.result, datasetCount: json.count });
})
.catch(() => {
this.props.addDangerToast(
t('An error occurred while fetching datasets'),
);
})
.finally(() => {
this.setState({ loading: false });
});
};
updateFilters = () => {
const { filterOperators, owners, databases } = this.state;
const convertFilter = ({
name: label,
operator,
}: {
name: string;
operator: string;
}) => ({ label, value: operator });
this.setState({
filters: [
{
Header: 'Database',
id: 'database',
input: 'select',
operators: filterOperators.database.map(convertFilter),
selects: databases.map(({ text: label, value }) => ({
label,
value,
})),
},
{
Header: 'Schema',
id: 'schema',
operators: filterOperators.schema.map(convertFilter),
},
{
Header: 'Table Name',
id: 'table_name',
operators: filterOperators.table_name.map(convertFilter),
},
{
Header: 'Owners',
id: 'owners',
input: 'select',
operators: filterOperators.owners.map(convertFilter),
selects: owners.map(({ text: label, value }) => ({ label, value })),
},
{
Header: 'SQL Lab View',
id: 'is_sqllab_view',
input: 'checkbox',
operators: filterOperators.is_sqllab_view.map(convertFilter),
},
],
});
};
render() {
const { datasets, datasetCount, loading, filters } = this.state;
return (
<>
<SubMenu {...this.menu} canCreate={this.canCreate} />
<ConfirmStatusChange
title={t('Please confirm')}
description={t(
'Are you sure you want to delete the selected datasets?',
)}
onConfirm={this.handleBulkDatasetDelete}
>
{confirmDelete => {
const bulkActions = [];
if (this.canDelete) {
bulkActions.push({
key: 'delete',
name: (
<>
<i className="fa fa-trash" /> {t('Delete')}
</>
),
onSelect: confirmDelete,
});
}
return (
return (
<>
<SubMenu {...menu} canCreate={canCreate()} />
<ConfirmStatusChange
title={t('Please confirm')}
description={t(
'Are you sure you want to delete the selected datasets?',
)}
onConfirm={handleBulkDatasetDelete}
>
{confirmDelete => {
const bulkActions = [];
if (canDelete()) {
bulkActions.push({
key: 'delete',
name: (
<>
<i className="fa fa-trash" /> {t('Delete')}
</>
),
onSelect: confirmDelete,
});
}
return (
<>
{datasetCurrentlyDeleting && (
<DeleteModal
description={t(
`The dataset ${datasetCurrentlyDeleting.table_name} is linked to
${datasetCurrentlyDeleting.chart_count} charts that appear on
${datasetCurrentlyDeleting.dashboard_count} dashboards.
Are you sure you want to continue? Deleting the dataset will break
those objects.`,
)}
onConfirm={() =>
handleDatasetDelete(datasetCurrentlyDeleting)
}
onHide={closeDatasetDeleteModal}
open
title={t('Delete Dataset?')}
/>
)}
<ListView
className="dataset-list-view"
columns={this.columns}
columns={columns}
data={datasets}
count={datasetCount}
pageSize={PAGE_SIZE}
fetchData={this.fetchData}
fetchData={fetchData}
loading={loading}
initialSort={this.initialSort}
filters={filters}
initialSort={initialSort}
filters={currentFilters}
bulkActions={bulkActions}
/>
);
}}
</ConfirmStatusChange>
</>
);
}
}
</>
);
}}
</ConfirmStatusChange>
</>
);
};
export default withToasts(DatasetList);

View File

@ -16,14 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import React, { FunctionComponent, useState } from 'react';
import styled from '@superset-ui/style';
import { SupersetClient } from '@superset-ui/connection';
import { t } from '@superset-ui/translation';
import { isEmpty, isNil } from 'lodash';
import Icon from 'src/components/Icon';
import TableSelector from 'src/components/TableSelector';
import Modal from './Modal';
import Modal from 'src/components/Modal';
import withToasts from '../../messageToasts/enhancers/withToasts';
interface DatasetModalProps {
@ -33,35 +33,29 @@ interface DatasetModalProps {
show: boolean;
}
interface DatasetModalState {
datasourceId?: number;
disableSave: boolean;
schema: string;
tableName: string;
}
const StyledIcon = styled(Icon)`
margin: auto 10px auto 0;
`;
class DatasetModal extends React.PureComponent<
DatasetModalProps,
DatasetModalState
> {
constructor(props: DatasetModalProps) {
super(props);
this.onSave = this.onSave.bind(this);
this.onChange = this.onChange.bind(this);
const TableSelectorContainer = styled.div`
.TableSelector {
padding-bottom: 340px;
width: 65%;
}
`;
state: DatasetModalState = {
datasourceId: undefined,
disableSave: true,
schema: '',
tableName: '',
};
const DatasetModal: FunctionComponent<DatasetModalProps> = ({
addDangerToast,
addSuccessToast,
onHide,
show,
}) => {
const [datasourceId, setDatasourceId] = useState<number | null>(null);
const [disableSave, setDisableSave] = useState(true);
const [currentSchema, setSchema] = useState('');
const [currentTableName, setTableName] = useState('');
onChange({
const onChange = ({
dbId,
schema,
tableName,
@ -69,61 +63,62 @@ class DatasetModal extends React.PureComponent<
dbId: number;
schema: string;
tableName: string;
}) {
const disableSave = isNil(dbId) || isEmpty(schema) || isEmpty(tableName);
this.setState({
datasourceId: dbId,
disableSave,
schema,
tableName,
});
}
}) => {
setDatasourceId(dbId);
setDisableSave(isNil(dbId) || isEmpty(schema) || isEmpty(tableName));
setSchema(schema);
setTableName(tableName);
};
onSave() {
const { datasourceId, schema, tableName } = this.state;
const data = { database: datasourceId, schema, table_name: tableName };
const onSave = () => {
const data = {
database: datasourceId,
schema: currentSchema,
table_name: currentTableName,
};
SupersetClient.post({
endpoint: '/api/v1/dataset/',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
})
.then(() => {
this.props.addSuccessToast(t('The dataset has been saved'));
this.props.onHide();
addSuccessToast(t('The dataset has been saved'));
onHide();
})
.catch(e => {
this.props.addDangerToast(t('Error while saving dataset'));
addDangerToast(t('Error while saving dataset'));
console.error(e);
});
}
};
render() {
return (
<Modal
disableSave={this.state.disableSave}
onHide={this.props.onHide}
onSave={this.onSave}
show={this.props.show}
title={
<>
<StyledIcon name="warning" />
{t('Add Dataset')}
</>
}
>
return (
<Modal
disablePrimaryButton={disableSave}
onHandledPrimaryAction={onSave}
onHide={onHide}
primaryButtonName={t('Add')}
show={show}
title={
<>
<StyledIcon name="warning" />
{t('Add Dataset')}
</>
}
>
<TableSelectorContainer>
<TableSelector
clearable={false}
dbId={this.state.datasourceId}
dbId={datasourceId}
formMode
handleError={this.props.addDangerToast}
onChange={this.onChange}
schema={this.state.schema}
handleError={addDangerToast}
onChange={onChange}
schema={currentSchema}
sqlLabMode={false}
tableName={this.state.tableName}
tableName={currentTableName}
/>
</Modal>
);
}
}
</TableSelectorContainer>
</Modal>
);
};
export default withToasts(DatasetModal);