From 80b06f682727338fc6ce7e804466620cffbcc13c Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Fri, 10 Jul 2020 14:23:17 -0700 Subject: [PATCH] feat: update delete modal for dataset (#10258) * update delete modal for dataset * update datasetList to use hooks * fix typo on dataset delete modal --- .../components/ConfirmStatusChange_spec.jsx | 8 +- .../src/components/ConfirmStatusChange.tsx | 80 ++- .../src/components/DeleteModal.tsx | 80 +++ .../datasetList => components}/Modal.tsx | 25 +- .../src/views/datasetList/Button.tsx | 7 +- .../src/views/datasetList/DatasetList.tsx | 484 +++++++++--------- .../src/views/datasetList/DatasetModal.tsx | 121 +++-- 7 files changed, 448 insertions(+), 357 deletions(-) create mode 100644 superset-frontend/src/components/DeleteModal.tsx rename superset-frontend/src/{views/datasetList => components}/Modal.tsx (82%) diff --git a/superset-frontend/spec/javascripts/components/ConfirmStatusChange_spec.jsx b/superset-frontend/spec/javascripts/components/ConfirmStatusChange_spec.jsx index d678dd8427..981a7f0019 100644 --- a/superset-frontend/spec/javascripts/components/ConfirmStatusChange_spec.jsx +++ b/superset-frontend/spec/javascripts/components/ConfirmStatusChange_spec.jsx @@ -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', () => { )} , + { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }, ); it('opens a confirm modal', () => { diff --git a/superset-frontend/src/components/ConfirmStatusChange.tsx b/superset-frontend/src/components/ConfirmStatusChange.tsx index 30d20a19e2..45ad4e94f5 100644 --- a/superset-frontend/src/components/ConfirmStatusChange.tsx +++ b/superset-frontend/src/components/ConfirmStatusChange.tsx @@ -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 { - public state = { - callbackArgs: [], - open: false, - }; +export default function ConfirmStatusChange({ + title, + description, + onConfirm, + children, +}: ConfirmStatusChangeProps) { + const [open, setOpen] = useState(false); + const [currentCallbackArgs, setCurrentCallbackArgs] = useState([]); - 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)} - - {this.props.title} - {this.props.description} - - - - - - - ); - } + const confirm = () => { + onConfirm(...currentCallbackArgs); + hide(); + }; + + return ( + <> + {children && children(showConfirm)} + + + ); } diff --git a/superset-frontend/src/components/DeleteModal.tsx b/superset-frontend/src/components/DeleteModal.tsx new file mode 100644 index 0000000000..69a81be1a5 --- /dev/null +++ b/superset-frontend/src/components/DeleteModal.tsx @@ -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 ( + + {description} + + + ) => + setDisableChange(event.target.value !== 'DELETE') + } + /> + + + ); +} diff --git a/superset-frontend/src/views/datasetList/Modal.tsx b/superset-frontend/src/components/Modal.tsx similarity index 82% rename from superset-frontend/src/views/datasetList/Modal.tsx rename to superset-frontend/src/components/Modal.tsx index c8c05888a9..fcaba9f20b 100644 --- a/superset-frontend/src/views/datasetList/Modal.tsx +++ b/superset-frontend/src/components/Modal.tsx @@ -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({ - diff --git a/superset-frontend/src/views/datasetList/Button.tsx b/superset-frontend/src/views/datasetList/Button.tsx index 7fbaaf0ab4..f0125e99c5 100644 --- a/superset-frontend/src/views/datasetList/Button.tsx +++ b/superset-frontend/src/views/datasetList/Button.tsx @@ -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({ diff --git a/superset-frontend/src/views/datasetList/DatasetList.tsx b/superset-frontend/src/views/datasetList/DatasetList.tsx index 0d0352765c..cc6fe46206 100644 --- a/superset-frontend/src/views/datasetList/DatasetList.tsx +++ b/superset-frontend/src/views/datasetList/DatasetList.tsx @@ -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 { - static propTypes = { - addDangerToast: PropTypes.func.isRequired, +const DatasetList: FunctionComponent = ({ + 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([]); + const [currentFilters, setCurrentFilters] = useState([]); + const [ + lastFetchDataConfig, + setLastFetchDataConfig, + ] = useState(null); + const [loading, setLoading] = useState(true); + const [currentOwners, setCurrentOwners] = useState< + { text: string; value: number }[] + >([]); + const [permissions, setPermissions] = useState([]); + + 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 { { 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 { } }, ); - } + }; - 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 { }, { 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 { - {this.canDelete && ( - - {t('Are you sure you want to delete ')}{' '} - {original.table_name}? - - } - onConfirm={handleDelete} + {canDelete && ( + - {confirmDelete => ( - - - - - - )} - + + + + )} - {this.canEdit && ( + + {canEdit() && ( { }, ]; - menu = { + const menu = { name: t('Data'), createButton: { name: t('Dataset'), @@ -383,194 +436,147 @@ class DatasetList extends React.PureComponent { ], }; - 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 ( - <> - - - {confirmDelete => { - const bulkActions = []; - if (this.canDelete) { - bulkActions.push({ - key: 'delete', - name: ( - <> - {t('Delete')} - - ), - onSelect: confirmDelete, - }); - } - return ( + return ( + <> + + + {confirmDelete => { + const bulkActions = []; + if (canDelete()) { + bulkActions.push({ + key: 'delete', + name: ( + <> + {t('Delete')} + + ), + onSelect: confirmDelete, + }); + } + return ( + <> + {datasetCurrentlyDeleting && ( + + handleDatasetDelete(datasetCurrentlyDeleting) + } + onHide={closeDatasetDeleteModal} + open + title={t('Delete Dataset?')} + /> + )} - ); - }} - - - ); - } -} + + ); + }} + + + ); +}; export default withToasts(DatasetList); diff --git a/superset-frontend/src/views/datasetList/DatasetModal.tsx b/superset-frontend/src/views/datasetList/DatasetModal.tsx index 292e6f59d6..0e43d26ecf 100644 --- a/superset-frontend/src/views/datasetList/DatasetModal.tsx +++ b/superset-frontend/src/views/datasetList/DatasetModal.tsx @@ -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 = ({ + addDangerToast, + addSuccessToast, + onHide, + show, +}) => { + const [datasourceId, setDatasourceId] = useState(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 ( - - - {t('Add Dataset')} - - } - > + return ( + + + {t('Add Dataset')} + + } + > + - - ); - } -} + + + ); +}; export default withToasts(DatasetModal);