Migrate Bootstrap Alert to AntD (#12101) (#12122)
diff --git a/superset-frontend/.eslintrc.js b/superset-frontend/.eslintrc.js
index 0558269..46970952 100644
--- a/superset-frontend/.eslintrc.js
+++ b/superset-frontend/.eslintrc.js
@@ -188,6 +188,7 @@
],
'jest/consistent-test-it': 'error',
'no-only-tests/no-only-tests': 'error',
+ '@typescript-eslint/no-non-null-assertion': 0,
},
},
],
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
index 64bb8fd..e8c1542 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
@@ -29,7 +29,7 @@
it('should show validator error when no metric', () => {
const formData = { ...LINE_CHART_DEFAULTS, metrics: [] };
cy.visitChartByParams(JSON.stringify(formData));
- cy.get('.alert-warning').contains(`"Metrics" cannot be empty`);
+ cy.get('.ant-alert-warning').contains(`"Metrics" cannot be empty`);
});
it('should preload mathjs', () => {
@@ -43,7 +43,7 @@
it('should not show validator error when metric added', () => {
const formData = { ...LINE_CHART_DEFAULTS, metrics: [] };
cy.visitChartByParams(JSON.stringify(formData));
- cy.get('.alert-warning').contains(`"Metrics" cannot be empty`);
+ cy.get('.ant-alert-warning').contains(`"Metrics" cannot be empty`);
cy.get('.text-danger').contains('Metrics');
cy.get('[data-test=metrics]')
@@ -58,14 +58,14 @@
cy.get('[data-test="AdhocMetricEdit#save"]').contains('Save').click();
cy.get('.text-danger').should('not.exist');
- cy.get('.alert-warning').should('not.exist');
+ cy.get('.ant-alert-warning').should('not.exist');
});
it('should allow negative values in Y bounds', () => {
cy.get('#controlSections-tab-display').click();
cy.get('span').contains('Y Axis Bounds').scrollIntoView();
cy.get('input[placeholder="Min"]').type('-0.1', { delay: 100 });
- cy.get('.alert-warning').should('not.exist');
+ cy.get('.ant-alert-warning').should('not.exist');
});
it('should allow type to search color schemes', () => {
diff --git a/superset-frontend/spec/helpers/theming.ts b/superset-frontend/spec/helpers/theming.ts
index 2ec019b..9815e34 100644
--- a/superset-frontend/spec/helpers/theming.ts
+++ b/superset-frontend/spec/helpers/theming.ts
@@ -52,5 +52,5 @@
theme: supersetTheme,
...options?.wrappingComponentProps,
},
- }).dive();
+ });
}
diff --git a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
index 866b8a3..5af36f3 100644
--- a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
+++ b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
@@ -296,11 +296,7 @@
});
it('allows disabling bulkSelect', () => {
- wrapper
- .find('[data-test="bulk-select-controls"]')
- .at(0)
- .props()
- .onDismiss();
+ wrapper.find('[data-test="bulk-select-controls"]').at(0).props().onClose();
expect(mockedProps.disableBulkSelect).toHaveBeenCalled();
});
diff --git a/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx
index 7a2c6c7..fd6952b 100644
--- a/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx
+++ b/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx
@@ -21,7 +21,7 @@
import ModalTrigger from 'src/components/ModalTrigger';
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
-import { Alert } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
const getMountWrapper = props =>
diff --git a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx
index 69082c5..c871a6a 100644
--- a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx
+++ b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx
@@ -21,8 +21,8 @@
import { act } from 'react-dom/test-utils';
import { ReactWrapper } from 'enzyme';
import { Provider } from 'react-redux';
-import Alert from 'react-bootstrap/lib/Alert';
import { FilterConfigModal } from 'src/dashboard/components/nativeFilters/FilterConfigModal/FilterConfigModal';
+import Alert from 'src/components/Alert';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import { mockStore } from 'spec/fixtures/mockStore';
diff --git a/superset-frontend/spec/javascripts/messageToasts/components/Toast_spec.jsx b/superset-frontend/spec/javascripts/messageToasts/components/Toast_spec.jsx
index fb61656..fa5cae1 100644
--- a/superset-frontend/spec/javascripts/messageToasts/components/Toast_spec.jsx
+++ b/superset-frontend/spec/javascripts/messageToasts/components/Toast_spec.jsx
@@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Alert } from 'react-bootstrap';
import React from 'react';
import { mount } from 'enzyme';
import Toast from 'src/messageToasts/components/Toast';
@@ -31,20 +30,19 @@
const setup = overrideProps => mount(<Toast {...props} {...overrideProps} />);
describe('Toast', () => {
- it('should render an Alert', () => {
+ it('should render', () => {
const wrapper = setup();
- expect(wrapper.find(Alert)).toExist();
+ expect(wrapper.find('[data-test="toast-container"]')).toExist();
});
- it('should render toastText within the alert', () => {
+ it('should render toastText within the div', () => {
const wrapper = setup();
- const alert = wrapper.find(Alert);
-
- expect(alert.childAt(0).childAt(1).text()).toBe(props.toast.text);
+ const container = wrapper.find('[data-test="toast-container"]');
+ expect(container.hostNodes().childAt(1).text()).toBe(props.toast.text);
});
- it('should call onCloseToast upon alert dismissal', async () => {
- await act(
+ it('should call onCloseToast upon toast dismissal', async () =>
+ act(
() =>
new Promise(done => {
const onCloseToast = id => {
@@ -53,13 +51,7 @@
};
const wrapper = setup({ onCloseToast });
- const handleClosePress = wrapper.find('[label="Close alert"]').props()
- .onClick;
-
- const alertProps = wrapper.find(Alert).props();
- expect(alertProps.onDismiss).toBe(handleClosePress);
- handleClosePress(); // there is a timeout for onCloseToast to be called
+ wrapper.find('[data-test="close-button"]').props().onClick();
}),
- );
- });
+ ));
});
diff --git a/superset-frontend/spec/javascripts/sqllab/ResultSet_spec.jsx b/superset-frontend/spec/javascripts/sqllab/ResultSet_spec.jsx
index a55559a..52f3566 100644
--- a/superset-frontend/spec/javascripts/sqllab/ResultSet_spec.jsx
+++ b/superset-frontend/spec/javascripts/sqllab/ResultSet_spec.jsx
@@ -18,10 +18,13 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
+import { styledMount } from 'spec/helpers/theming';
+import { Provider } from 'react-redux';
import sinon from 'sinon';
-import { Alert } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import ProgressBar from 'src/common/components/ProgressBar';
-
+import configureStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
import FilterableTable from 'src/components/FilterableTable/FilterableTable';
import ExploreResultsButton from 'src/SqlLab/components/ExploreResultsButton';
import ResultSet from 'src/SqlLab/components/ResultSet';
@@ -33,8 +36,12 @@
queries,
runningQuery,
stoppedQuery,
+ initialState,
} from './fixtures';
+const mockStore = configureStore([thunk]);
+const store = mockStore(initialState);
+
describe('ResultSet', () => {
const clearQuerySpy = sinon.spy();
const fetchQuerySpy = sinon.spy();
@@ -105,17 +112,18 @@
expect(wrapper.find(ExploreResultsButton)).toExist();
});
it('should render empty results', () => {
- const wrapper = shallow(<ResultSet {...mockedProps} />);
- const emptyResults = {
- ...queries[0],
- results: {
- data: [],
- },
+ const props = {
+ ...mockedProps,
+ query: { ...mockedProps.query, results: { data: [] } },
};
- wrapper.setProps({ query: emptyResults });
+ const wrapper = styledMount(
+ <Provider store={store}>
+ <ResultSet {...props} />
+ </Provider>,
+ );
expect(wrapper.find(FilterableTable)).not.toExist();
expect(wrapper.find(Alert)).toExist();
- expect(wrapper.find(Alert).shallow().text()).toBe(
+ expect(wrapper.find(Alert).render().text()).toBe(
'The query returned no data',
);
});
diff --git a/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx b/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx
index 0218486..b7c34c0 100644
--- a/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx
+++ b/superset-frontend/spec/javascripts/sqllab/SouthPane_spec.jsx
@@ -81,12 +81,12 @@
let wrapper;
it('should render offline when the state is offline', () => {
- wrapper = getWrapper();
+ wrapper = getWrapper().dive();
wrapper.setProps({ offline: true });
expect(wrapper.childAt(0).text()).toBe(STATUS_OPTIONS.offline);
});
it('should pass latest query down to ResultSet component', () => {
- wrapper = getWrapper();
+ wrapper = getWrapper().dive();
expect(wrapper.find(ResultSet)).toExist();
expect(wrapper.find(ResultSet).props().query.id).toEqual(
mockedProps.latestQueryId,
diff --git a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton.jsx b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton.jsx
index d5424cd..8fa3bd0 100644
--- a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton.jsx
+++ b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton.jsx
@@ -18,7 +18,7 @@
*/
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
-import { Alert } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import { t } from '@superset-ui/core';
import TableView from 'src/components/TableView';
@@ -61,9 +61,11 @@
const renderModalBody = () => {
if (props.queryCostEstimate.error !== null) {
return (
- <Alert key="query-estimate-error" bsStyle="danger">
- {props.queryCostEstimate.error}
- </Alert>
+ <Alert
+ key="query-estimate-error"
+ type="error"
+ message={props.queryCostEstimate.error}
+ />
);
}
if (props.queryCostEstimate.completed) {
diff --git a/superset-frontend/src/SqlLab/components/ExploreResultsButton.jsx b/superset-frontend/src/SqlLab/components/ExploreResultsButton.jsx
index fc8c8ac..1c94c8b 100644
--- a/superset-frontend/src/SqlLab/components/ExploreResultsButton.jsx
+++ b/superset-frontend/src/SqlLab/components/ExploreResultsButton.jsx
@@ -21,7 +21,7 @@
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
-import { Alert } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import { t } from '@superset-ui/core';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import shortid from 'shortid';
@@ -102,25 +102,32 @@
renderTimeoutWarning() {
return (
- <Alert bsStyle="warning">
- {t(
- 'This query took %s seconds to run, ',
- Math.round(this.getQueryDuration()),
- ) +
- t(
- 'and the explore view times out at %s seconds ',
- this.props.timeout,
- ) +
- t(
- 'following this flow will most likely lead to your query timing out. ',
- ) +
- t(
- 'We recommend your summarize your data further before following that flow. ',
- ) +
- t('If activated you can use the ')}
- <strong>CREATE TABLE AS </strong>
- {t('feature to store a summarized data set that you can then explore.')}
- </Alert>
+ <Alert
+ type="warning"
+ message={
+ <>
+ {t(
+ 'This query took %s seconds to run, ',
+ Math.round(this.getQueryDuration()),
+ ) +
+ t(
+ 'and the explore view times out at %s seconds ',
+ this.props.timeout,
+ ) +
+ t(
+ 'following this flow will most likely lead to your query timing out. ',
+ ) +
+ t(
+ 'We recommend your summarize your data further before following that flow. ',
+ ) +
+ t('If activated you can use the ')}
+ <strong>CREATE TABLE AS </strong>
+ {t(
+ 'feature to store a summarized data set that you can then explore.',
+ )}
+ </>
+ }
+ />
);
}
diff --git a/superset-frontend/src/SqlLab/components/QueryHistory.jsx b/superset-frontend/src/SqlLab/components/QueryHistory.jsx
index 59c1e72..e00dd2a 100644
--- a/superset-frontend/src/SqlLab/components/QueryHistory.jsx
+++ b/superset-frontend/src/SqlLab/components/QueryHistory.jsx
@@ -18,7 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
-import { Alert } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import { t } from '@superset-ui/core';
import QueryTable from './QueryTable';
@@ -49,7 +49,7 @@
/>
);
}
- return <Alert bsStyle="info">{t('No query history yet...')}</Alert>;
+ return <Alert type="info" message={t('No query history yet...')} />;
};
QueryHistory.propTypes = propTypes;
diff --git a/superset-frontend/src/SqlLab/components/ResultSet.tsx b/superset-frontend/src/SqlLab/components/ResultSet.tsx
index 19b6e92..bf4089f 100644
--- a/superset-frontend/src/SqlLab/components/ResultSet.tsx
+++ b/superset-frontend/src/SqlLab/components/ResultSet.tsx
@@ -17,8 +17,8 @@
* under the License.
*/
import React, { CSSProperties } from 'react';
-import { Alert } from 'react-bootstrap';
import ButtonGroup from 'src/components/ButtonGroup';
+import Alert from 'src/components/Alert';
import ProgressBar from 'src/common/components/ProgressBar';
import moment from 'moment';
import { RadioChangeEvent } from 'antd/lib/radio';
@@ -498,7 +498,7 @@
}
if (query.state === 'stopped') {
- return <Alert bsStyle="warning">Query was stopped</Alert>;
+ return <Alert type="warning" message={t('Query was stopped')} />;
}
if (query.state === 'failed') {
return (
@@ -522,31 +522,36 @@
}
return (
<div>
- <Alert bsStyle="info">
- {t(object)} [
- <strong>
- {tempSchema ? `${tempSchema}.` : ''}
- {tempTable}
- </strong>
- ] {t('was created')}
- <ButtonGroup>
- <Button
- buttonSize="small"
- className="m-r-5"
- onClick={() => this.popSelectStar(tempSchema, tempTable)}
- >
- {t('Query in a new tab')}
- </Button>
- <ExploreCtasResultsButton
- // @ts-ignore Redux types are difficult to work with, ignoring for now
- table={tempTable}
- schema={tempSchema}
- dbId={exploreDBId}
- database={this.props.database}
- actions={this.props.actions}
- />
- </ButtonGroup>
- </Alert>
+ <Alert
+ type="info"
+ message={
+ <>
+ {t(object)} [
+ <strong>
+ {tempSchema ? `${tempSchema}.` : ''}
+ {tempTable}
+ </strong>
+ ] {t('was created')}
+ <ButtonGroup>
+ <Button
+ buttonSize="small"
+ className="m-r-5"
+ onClick={() => this.popSelectStar(tempSchema, tempTable)}
+ >
+ {t('Query in a new tab')}
+ </Button>
+ <ExploreCtasResultsButton
+ // @ts-ignore Redux types are difficult to work with, ignoring for now
+ table={tempTable}
+ schema={tempSchema}
+ dbId={exploreDBId}
+ database={this.props.database}
+ actions={this.props.actions}
+ />
+ </ButtonGroup>
+ </>
+ }
+ />
</div>
);
}
@@ -578,7 +583,7 @@
}
if (data && data.length === 0) {
return (
- <Alert bsStyle="warning">{t('The query returned no data')}</Alert>
+ <Alert type="warning" message={t('The query returned no data')} />
);
}
}
@@ -642,7 +647,7 @@
<div>{!progressBar && <Loading position="normal" />}</div>
<QueryStateLabel query={query} />
<div>
- {progressMsg && <Alert bsStyle="success">{progressMsg}</Alert>}
+ {progressMsg && <Alert type="success" message={progressMsg} />}
</div>
<div>{progressBar}</div>
<div>{trackingUrl}</div>
diff --git a/superset-frontend/src/SqlLab/components/SouthPane.jsx b/superset-frontend/src/SqlLab/components/SouthPane.jsx
index e2ffa0a..b0182ed 100644
--- a/superset-frontend/src/SqlLab/components/SouthPane.jsx
+++ b/superset-frontend/src/SqlLab/components/SouthPane.jsx
@@ -19,7 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
-import { Alert } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import Tabs from 'src/common/components/Tabs';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
@@ -120,9 +120,12 @@
!latestQuery.results
) {
results = (
- <Alert bsStyle="warning">
- {t('No stored results found, you need to re-run your query')}
- </Alert>
+ <Alert
+ type="warning"
+ message={t(
+ 'No stored results found, you need to re-run your query',
+ )}
+ />
);
} else if (
Date.now() - latestQuery.startDttm <=
@@ -142,7 +145,7 @@
}
} else {
results = (
- <Alert bsStyle="info">{t('Run a query to display results here')}</Alert>
+ <Alert type="info" message={t('Run a query to display results here')} />
);
}
const dataPreviewTabs = props.dataPreviewQueries.map(query => (
diff --git a/superset-frontend/src/chart/Chart.jsx b/superset-frontend/src/chart/Chart.jsx
index 3f1596c..520378a 100644
--- a/superset-frontend/src/chart/Chart.jsx
+++ b/superset-frontend/src/chart/Chart.jsx
@@ -18,7 +18,7 @@
*/
import PropTypes from 'prop-types';
import React from 'react';
-import { Alert } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import { styled, logging } from '@superset-ui/core';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
@@ -193,9 +193,11 @@
}
if (errorMessage) {
return (
- <Alert data-test="alert-warning" bsStyle="warning">
- {errorMessage}
- </Alert>
+ <Alert
+ data-test="alert-warning"
+ message={errorMessage}
+ type="warning"
+ />
);
}
diff --git a/superset-frontend/src/common/components/index.tsx b/superset-frontend/src/common/components/index.tsx
index c10be85..65591e5 100644
--- a/superset-frontend/src/common/components/index.tsx
+++ b/superset-frontend/src/common/components/index.tsx
@@ -28,7 +28,6 @@
*/
// eslint-disable-next-line no-restricted-imports
export {
- Alert,
AutoComplete,
Avatar,
Button,
@@ -57,6 +56,7 @@
Tooltip,
Input as AntdInput,
} from 'antd';
+export { default as Alert, AlertProps } from 'antd/lib/alert';
export { TreeProps } from 'antd/lib/tree';
export { FormInstance } from 'antd/lib/form';
export { RadioChangeEvent } from 'antd/lib/radio';
diff --git a/superset-frontend/src/components/Alert/Alert.stories.tsx b/superset-frontend/src/components/Alert/Alert.stories.tsx
new file mode 100644
index 0000000..4b4f86ea
--- /dev/null
+++ b/superset-frontend/src/components/Alert/Alert.stories.tsx
@@ -0,0 +1,100 @@
+/**
+ * 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 React from 'react';
+import Alert, { AlertProps } from './index';
+
+type AlertType = Pick<AlertProps, 'type'>;
+type AlertTypeValue = AlertType[keyof AlertType];
+
+const types: AlertTypeValue[] = ['info', 'error', 'warning', 'success'];
+
+const smallText = 'Lorem ipsum dolor sit amet';
+
+const bigText =
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
+ 'Nam id porta neque, a vehicula orci. Maecenas rhoncus elit sit amet ' +
+ 'purus convallis placerat in at nunc. Nulla nec viverra augue.';
+
+export default {
+ title: 'Alert',
+ component: Alert,
+};
+
+export const AlertGallery = () => (
+ <>
+ {types.map(type => (
+ <div key={type} style={{ marginBottom: 40, width: 600 }}>
+ <h4>{type}</h4>
+ <Alert
+ type={type}
+ showIcon
+ closable
+ message={bigText}
+ style={{ marginBottom: 20 }}
+ />
+ <Alert
+ type={type}
+ showIcon
+ message={smallText}
+ description={bigText}
+ closable
+ />
+ </div>
+ ))}
+ </>
+);
+
+AlertGallery.story = {
+ parameters: {
+ actions: {
+ disabled: true,
+ },
+ controls: {
+ disabled: true,
+ },
+ knobs: {
+ disabled: true,
+ },
+ },
+};
+
+export const InteractiveAlert = (args: AlertProps) => <Alert {...args} />;
+
+InteractiveAlert.args = {
+ closable: true,
+ type: 'info',
+ message: smallText,
+ description: bigText,
+ showIcon: true,
+};
+
+InteractiveAlert.argTypes = {
+ onClose: { action: 'onClose' },
+ type: {
+ control: { type: 'select', options: types },
+ },
+};
+
+InteractiveAlert.story = {
+ parameters: {
+ knobs: {
+ disabled: true,
+ },
+ },
+};
diff --git a/superset-frontend/src/components/Alert/Alert.test.tsx b/superset-frontend/src/components/Alert/Alert.test.tsx
new file mode 100644
index 0000000..eeb23b3
--- /dev/null
+++ b/superset-frontend/src/components/Alert/Alert.test.tsx
@@ -0,0 +1,68 @@
+/**
+ * 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 React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import userEvent from '@testing-library/user-event';
+import Alert, { AlertProps } from 'src/components/Alert';
+
+type AlertType = Pick<AlertProps, 'type'>;
+type AlertTypeValue = AlertType[keyof AlertType];
+
+test('renders with default props', () => {
+ render(<Alert message="Message" />);
+ expect(screen.getByRole('alert')).toHaveTextContent('Message');
+ expect(screen.queryByLabelText(`info icon`)).toBeInTheDocument();
+ expect(screen.queryByLabelText('close icon')).toBeInTheDocument();
+});
+
+test('renders each type', () => {
+ const types: AlertTypeValue[] = ['info', 'error', 'warning', 'success'];
+ types.forEach(type => {
+ render(<Alert type={type} message="Message" />);
+ expect(screen.queryByLabelText(`${type} icon`)).toBeInTheDocument();
+ });
+});
+
+test('renders without close button', () => {
+ render(<Alert message="Message" closable={false} />);
+ expect(screen.queryByLabelText('close icon')).not.toBeInTheDocument();
+});
+
+test('disappear when closed', () => {
+ render(<Alert message="Message" />);
+ userEvent.click(screen.queryByLabelText('close icon')!);
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+});
+
+test('renders without icon', () => {
+ const type = 'info';
+ render(<Alert type={type} message="Message" showIcon={false} />);
+ expect(screen.queryByLabelText(`${type} icon`)).not.toBeInTheDocument();
+});
+
+test('renders message', () => {
+ render(<Alert message="Message" />);
+ expect(screen.getByRole('alert')).toHaveTextContent('Message');
+});
+
+test('renders message and description', () => {
+ render(<Alert message="Message" description="Description" />);
+ expect(screen.getByRole('alert')).toHaveTextContent('Message');
+ expect(screen.getByRole('alert')).toHaveTextContent('Description');
+});
diff --git a/superset-frontend/src/components/Alert/index.tsx b/superset-frontend/src/components/Alert/index.tsx
new file mode 100644
index 0000000..a80238a
--- /dev/null
+++ b/superset-frontend/src/components/Alert/index.tsx
@@ -0,0 +1,86 @@
+/**
+ * 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 React, { PropsWithChildren } from 'react';
+import {
+ Alert as AntdAlert,
+ AlertProps as AntdAlertProps,
+} from 'src/common/components';
+import { useTheme } from '@superset-ui/core';
+import Icon, { IconName } from 'src/components/Icon';
+
+export type AlertProps = PropsWithChildren<AntdAlertProps>;
+
+export default function Alert(props: AlertProps) {
+ const {
+ type = 'info',
+ description,
+ showIcon = true,
+ closable = true,
+ children,
+ } = props;
+
+ const theme = useTheme();
+ const { colors, typography } = theme;
+ const { alert, error, info, success } = colors;
+
+ let baseColor = info;
+ let iconName: IconName = 'info-solid';
+ if (type === 'error') {
+ baseColor = error;
+ iconName = 'error-solid';
+ } else if (type === 'warning') {
+ baseColor = alert;
+ iconName = 'alert-solid';
+ } else if (type === 'success') {
+ baseColor = success;
+ iconName = 'circle-check-solid';
+ }
+
+ return (
+ <AntdAlert
+ role="alert"
+ showIcon={showIcon}
+ icon={<Icon name={iconName} aria-label={`${type} icon`} />}
+ closeText={closable && <Icon name="x-small" aria-label="close icon" />}
+ css={{
+ padding: '6px 10px',
+ alignItems: 'flex-start',
+ border: 0,
+ backgroundColor: baseColor.light2,
+ '& .ant-alert-icon': {
+ marginRight: 10,
+ },
+ '& .ant-alert-message': {
+ color: baseColor.dark2,
+ fontSize: typography.sizes.m,
+ fontWeight: description
+ ? typography.weights.bold
+ : typography.weights.normal,
+ },
+ '& .ant-alert-description': {
+ color: baseColor.dark2,
+ fontSize: typography.sizes.m,
+ },
+ }}
+ {...props}
+ >
+ {children}
+ </AntdAlert>
+ );
+}
diff --git a/superset-frontend/src/components/Checkbox/Checkbox.test.tsx b/superset-frontend/src/components/Checkbox/Checkbox.test.tsx
index 7877b7e..5e493d3 100644
--- a/superset-frontend/src/components/Checkbox/Checkbox.test.tsx
+++ b/superset-frontend/src/components/Checkbox/Checkbox.test.tsx
@@ -43,7 +43,9 @@
const shallowWrapper = shallow(
<Checkbox style={{}} checked={false} onChange={() => true} />,
);
- expect(shallowWrapper.dive().dive().find(CheckboxUnchecked)).toExist();
+ expect(
+ shallowWrapper.dive().dive().dive().find(CheckboxUnchecked),
+ ).toExist();
});
});
@@ -52,7 +54,9 @@
const shallowWrapper = shallow(
<Checkbox style={{}} checked onChange={() => true} />,
);
- expect(shallowWrapper.dive().dive().find(CheckboxChecked)).toExist();
+ expect(
+ shallowWrapper.dive().dive().dive().find(CheckboxChecked),
+ ).toExist();
});
});
diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx
index 232ec2a..8d9182a 100644
--- a/superset-frontend/src/components/ListView/ListView.tsx
+++ b/superset-frontend/src/components/ListView/ListView.tsx
@@ -18,8 +18,8 @@
*/
import { t, styled } from '@superset-ui/core';
import React, { useEffect } from 'react';
-import { Alert } from 'react-bootstrap';
import { Empty } from 'src/common/components';
+import Alert from 'src/components/Alert';
import { ReactComponent as EmptyImage } from 'images/empty.svg';
import cx from 'classnames';
import Button from 'src/components/Button';
@@ -91,15 +91,12 @@
const BulkSelectWrapper = styled(Alert)`
border-radius: 0;
margin-bottom: 0;
- padding-top: 0;
- padding-bottom: 0;
- padding-right: ${({ theme }) => theme.gridUnit * 9}px;
color: #3d3d3d;
background-color: ${({ theme }) => theme.colors.primary.light4};
.selectedCopy {
display: inline-block;
- padding: ${({ theme }) => theme.gridUnit * 4}px 0;
+ padding: ${({ theme }) => theme.gridUnit * 2}px 0;
}
.deselect-all {
@@ -117,10 +114,6 @@
vertical-align: middle;
position: relative;
}
-
- .close {
- margin: ${({ theme }) => theme.gridUnit * 4}px 0;
- }
`;
const bulkSelectColumnConfig = {
@@ -330,40 +323,47 @@
{bulkSelectEnabled && (
<BulkSelectWrapper
data-test="bulk-select-controls"
- bsStyle="info"
- onDismiss={disableBulkSelect}
- >
- <div className="selectedCopy" data-test="bulk-select-copy">
- {renderBulkSelectCopy(selectedFlatRows)}
- </div>
- {Boolean(selectedFlatRows.length) && (
+ type="info"
+ closable
+ showIcon={false}
+ onClose={disableBulkSelect}
+ message={
<>
- <span
- data-test="bulk-select-deselect-all"
- role="button"
- tabIndex={0}
- className="deselect-all"
- onClick={() => toggleAllRowsSelected(false)}
- >
- {t('Deselect all')}
- </span>
- <div className="divider" />
- {bulkActions.map(action => (
- <Button
- data-test="bulk-select-action"
- key={action.key}
- buttonStyle={action.type}
- cta
- onClick={() =>
- action.onSelect(selectedFlatRows.map(r => r.original))
- }
- >
- {action.name}
- </Button>
- ))}
+ <div className="selectedCopy" data-test="bulk-select-copy">
+ {renderBulkSelectCopy(selectedFlatRows)}
+ </div>
+ {Boolean(selectedFlatRows.length) && (
+ <>
+ <span
+ data-test="bulk-select-deselect-all"
+ role="button"
+ tabIndex={0}
+ className="deselect-all"
+ onClick={() => toggleAllRowsSelected(false)}
+ >
+ {t('Deselect all')}
+ </span>
+ <div className="divider" />
+ {bulkActions.map(action => (
+ <Button
+ data-test="bulk-select-action"
+ key={action.key}
+ buttonStyle={action.type}
+ cta
+ onClick={() =>
+ action.onSelect(
+ selectedFlatRows.map(r => r.original),
+ )
+ }
+ >
+ {action.name}
+ </Button>
+ ))}
+ </>
+ )}
</>
- )}
- </BulkSelectWrapper>
+ }
+ />
)}
{viewMode === 'card' && (
<CardCollection
diff --git a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx
index d468687..1e21a45 100644
--- a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx
+++ b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx
@@ -19,7 +19,7 @@
import React, { RefObject } from 'react';
import Select from 'src/components/Select';
import { t, styled } from '@superset-ui/core';
-import { Alert } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import Button from 'src/components/Button';
import ModalTrigger from 'src/components/ModalTrigger';
@@ -124,11 +124,16 @@
/>
{showRefreshWarning && (
<RefreshWarningContainer>
- <Alert bsStyle="warning">
- <div>{refreshWarning}</div>
- <br />
- <strong>{t('Are you sure you want to proceed?')}</strong>
- </Alert>
+ <Alert
+ type="warning"
+ message={
+ <>
+ <div>{refreshWarning}</div>
+ <br />
+ <strong>{t('Are you sure you want to proceed?')}</strong>
+ </>
+ }
+ />
</RefreshWarningContainer>
)}
</div>
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/CancelConfirmationAlert.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/CancelConfirmationAlert.tsx
index 96d1307..875e631 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/CancelConfirmationAlert.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/CancelConfirmationAlert.tsx
@@ -17,48 +17,9 @@
* under the License.
*/
import React from 'react';
-import { styled, t } from '@superset-ui/core';
-import Alert from 'react-bootstrap/lib/Alert';
+import { t } from '@superset-ui/core';
+import Alert from 'src/components/Alert';
import Button from 'src/components/Button';
-import Icon from 'src/components/Icon';
-
-const StyledAlert = styled(Alert)`
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
- padding: ${({ theme }) => theme.gridUnit * 2}px;
-`;
-
-const StyledTextContainer = styled.div`
- display: flex;
- flex-direction: column;
- text-align: left;
- margin-right: ${({ theme }) => theme.gridUnit}px;
-`;
-
-const StyledTitleBox = styled.div`
- display: flex;
- align-items: center;
-`;
-
-const StyledAlertTitle = styled.span`
- font-weight: ${({ theme }) => theme.typography.weights.bold};
-`;
-
-const StyledAlertText = styled.p`
- margin-left: ${({ theme }) => theme.gridUnit * 9}px;
-`;
-
-const StyledButtonsContainer = styled.div`
- display: flex;
- flex-direction: row;
-`;
-
-const StyledAlertIcon = styled(Icon)`
- color: ${({ theme }) => theme.colors.alert.base};
- margin-right: ${({ theme }) => theme.gridUnit * 3}px;
-`;
export interface ConfirmationAlertProps {
title: string;
@@ -74,32 +35,35 @@
children,
}: ConfirmationAlertProps) {
return (
- <StyledAlert bsStyle="warning" key="alert">
- <StyledTextContainer>
- <StyledTitleBox>
- <StyledAlertIcon name="alert-solid" />
- <StyledAlertTitle>{title}</StyledAlertTitle>
- </StyledTitleBox>
- <StyledAlertText>{children}</StyledAlertText>
- </StyledTextContainer>
- <StyledButtonsContainer>
- <Button
- key="submit"
- buttonSize="small"
- buttonStyle="primary"
- onClick={onConfirm}
- >
- {t('Yes, cancel')}
- </Button>
- <Button
- key="cancel"
- buttonSize="small"
- buttonStyle="secondary"
- onClick={onDismiss}
- >
- {t('Keep editing')}
- </Button>
- </StyledButtonsContainer>
- </StyledAlert>
+ <Alert
+ type="warning"
+ key="alert"
+ message={title}
+ css={{
+ textAlign: 'left',
+ '& .ant-alert-action': { alignSelf: 'center' },
+ }}
+ description={children}
+ action={
+ <div css={{ display: 'flex' }}>
+ <Button
+ key="submit"
+ buttonSize="small"
+ buttonStyle="primary"
+ onClick={onConfirm}
+ >
+ {t('Yes, cancel')}
+ </Button>
+ <Button
+ key="cancel"
+ buttonSize="small"
+ buttonStyle="secondary"
+ onClick={onDismiss}
+ >
+ {t('Keep editing')}
+ </Button>
+ </div>
+ }
+ />
);
}
diff --git a/superset-frontend/src/datasource/ChangeDatasourceModal.tsx b/superset-frontend/src/datasource/ChangeDatasourceModal.tsx
index 5c144e4..0934a6d 100644
--- a/superset-frontend/src/datasource/ChangeDatasourceModal.tsx
+++ b/superset-frontend/src/datasource/ChangeDatasourceModal.tsx
@@ -23,7 +23,8 @@
useEffect,
useCallback,
} from 'react';
-import { Alert, FormControl, FormControlProps } from 'react-bootstrap';
+import { FormControl, FormControlProps } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import { SupersetClient, t, styled } from '@superset-ui/core';
import TableView, { EmptyWrapperType } from 'src/components/TableView';
import StyledModal from 'src/common/components/Modal';
@@ -246,9 +247,14 @@
<>
{!confirmChange && (
<>
- <Alert bsStyle="warning">
- <strong>{t('Warning!')}</strong> {CHANGE_WARNING_MSG}
- </Alert>
+ <Alert
+ type="warning"
+ message={
+ <>
+ <strong>{t('Warning!')}</strong> {CHANGE_WARNING_MSG}
+ </>
+ }
+ />
<div>
<FormControl
inputRef={ref => {
diff --git a/superset-frontend/src/datasource/DatasourceEditor.jsx b/superset-frontend/src/datasource/DatasourceEditor.jsx
index 5d3cf1c..29f5b70 100644
--- a/superset-frontend/src/datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/datasource/DatasourceEditor.jsx
@@ -18,8 +18,9 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
-import { Alert, Col, Well } from 'react-bootstrap';
+import { Col, Well } from 'react-bootstrap';
import { Radio } from 'src/common/components/Radio';
+import Alert from 'src/components/Alert';
import Badge from 'src/common/components/Badge';
import shortid from 'shortid';
import { styled, SupersetClient, t, supersetTheme } from '@superset-ui/core';
@@ -830,11 +831,17 @@
renderErrors() {
if (this.state.errors.length > 0) {
return (
- <Alert bsStyle="danger">
- {this.state.errors.map(err => (
- <div key={err}>{err}</div>
- ))}
- </Alert>
+ <Alert
+ css={theme => ({ marginBottom: theme.gridUnit * 4 })}
+ type="error"
+ message={
+ <>
+ {this.state.errors.map(err => (
+ <div key={err}>{err}</div>
+ ))}
+ </>
+ }
+ />
);
}
return null;
@@ -970,14 +977,19 @@
return (
<DatasourceContainer>
{this.renderErrors()}
- <div className="m-t-10">
- <Alert bsStyle="warning">
- <strong>{t('Be careful.')} </strong>
- {t(
- 'Changing these settings will affect all charts using this dataset, including charts owned by other people.',
- )}
- </Alert>
- </div>
+ <Alert
+ css={theme => ({ marginBottom: theme.gridUnit * 4 })}
+ type="warning"
+ message={
+ <>
+ {' '}
+ <strong>{t('Be careful.')} </strong>
+ {t(
+ 'Changing these settings will affect all charts using this dataset, including charts owned by other people.',
+ )}
+ </>
+ }
+ />
<Tabs
fullWidth={false}
id="table-tabs"
diff --git a/superset-frontend/src/datasource/DatasourceModal.tsx b/superset-frontend/src/datasource/DatasourceModal.tsx
index 109129f..1f4a178 100644
--- a/superset-frontend/src/datasource/DatasourceModal.tsx
+++ b/superset-frontend/src/datasource/DatasourceModal.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import React, { FunctionComponent, useState, useRef } from 'react';
-import { Alert } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import Button from 'src/components/Button';
import { styled, t, SupersetClient } from '@superset-ui/core';
@@ -138,22 +138,21 @@
setErrors(err);
};
- const closeDialog = () => {
- dialog.current?.destroy();
- };
-
const renderSaveDialog = () => (
<div>
- <Alert bsStyle="warning" className="pointer" onClick={closeDialog}>
- <div>
- <i className="fa fa-exclamation-triangle" />{' '}
- {t(`The dataset configuration exposed here
+ <Alert
+ css={theme => ({
+ marginTop: theme.gridUnit * 4,
+ marginBottom: theme.gridUnit * 4,
+ })}
+ type="warning"
+ showIcon
+ message={t(`The dataset configuration exposed here
affects all the charts using this dataset.
Be mindful that changing settings
here may affect other charts
in undesirable ways.`)}
- </div>
- </Alert>
+ />
{t('Are you sure you want to save and apply changes?')}
</div>
);
diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
index b084505..0a41a99 100644
--- a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
+++ b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
@@ -21,11 +21,11 @@
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
-import { Alert } from 'react-bootstrap';
import { t, styled, getChartControlPanelRegistry } from '@superset-ui/core';
import Tabs from 'src/common/components/Tabs';
import { Collapse } from 'src/common/components';
+import Alert from 'src/components/Alert';
import { PluginContext } from 'src/components/DynamicPlugins';
import Loading from 'src/components/Loading';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
@@ -278,17 +278,12 @@
return (
<Styles>
{this.props.alert && (
- <Alert bsStyle="warning">
- {this.props.alert}
- <i
- role="button"
- aria-label="Remove alert"
- tabIndex={0}
- className="fa fa-close pull-right"
- onClick={this.removeAlert}
- style={{ cursor: 'pointer' }}
- />
- </Alert>
+ <Alert
+ type="warning"
+ message={this.props.alert}
+ closable
+ onClose={this.removeAlert}
+ />
)}
<ControlPanelsTabs
id="controlSections"
diff --git a/superset-frontend/src/explore/components/SaveModal.tsx b/superset-frontend/src/explore/components/SaveModal.tsx
index 5d53074..bc19942 100644
--- a/superset-frontend/src/explore/components/SaveModal.tsx
+++ b/superset-frontend/src/explore/components/SaveModal.tsx
@@ -18,7 +18,8 @@
*/
/* eslint camelcase: 0 */
import React from 'react';
-import { Alert, FormControl, FormGroup } from 'react-bootstrap';
+import { FormControl, FormGroup } from 'react-bootstrap';
+import Alert from 'src/components/Alert';
import { JsonObject, t, styled } from '@superset-ui/core';
import ReactMarkdown from 'react-markdown';
import { Radio } from 'src/common/components/Radio';
@@ -206,17 +207,22 @@
>
<div data-test="save-modal-body">
{(this.state.alert || this.props.alert) && (
- <Alert>
- {this.state.alert ? this.state.alert : this.props.alert}
- <i
- role="button"
- aria-label="Remove alert"
- tabIndex={0}
- className="fa fa-close pull-right"
- onClick={this.removeAlert.bind(this)}
- style={{ cursor: 'pointer' }}
- />
- </Alert>
+ <Alert
+ type="warning"
+ message={
+ <>
+ {this.state.alert ? this.state.alert : this.props.alert}
+ <i
+ role="button"
+ aria-label="Remove alert"
+ tabIndex={0}
+ className="fa fa-close pull-right"
+ onClick={this.removeAlert.bind(this)}
+ style={{ cursor: 'pointer' }}
+ />
+ </>
+ }
+ />
)}
<FormGroup data-test="radio-group">
<Radio
diff --git a/superset-frontend/src/messageToasts/components/Toast.tsx b/superset-frontend/src/messageToasts/components/Toast.tsx
index f9b9bf7..a833b8b 100644
--- a/superset-frontend/src/messageToasts/components/Toast.tsx
+++ b/superset-frontend/src/messageToasts/components/Toast.tsx
@@ -16,16 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Alert } from 'react-bootstrap';
import { styled } from '@superset-ui/core';
import cx from 'classnames';
import Interweave from 'interweave';
import React, { useCallback, useEffect, useRef, useState } from 'react';
-import Icon from 'src/components/Icon';
+import Icon, { IconName } from 'src/components/Icon';
import { ToastType } from 'src/messageToasts/constants';
import { ToastMeta } from '../types';
-const ToastContianer = styled.div`
+const ToastContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
@@ -35,6 +34,10 @@
}
`;
+const StyledIcon = styled(Icon)`
+ min-width: ${({ theme }) => theme.gridUnit * 5}px;
+`;
+
interface ToastPresenterProps {
toast: ToastMeta;
onCloseToast: (id: string) => void;
@@ -73,26 +76,34 @@
};
}, [handleClosePress, toast.duration]);
+ let iconName: IconName = 'circle-check-solid';
+ let className = 'toast--success';
+ if (toast.toastType === ToastType.WARNING) {
+ iconName = 'warning-solid';
+ className = 'toast--warning';
+ } else if (toast.toastType === ToastType.DANGER) {
+ iconName = 'error-solid';
+ className = 'toast--danger';
+ } else if (toast.toastType === ToastType.INFO) {
+ iconName = 'info-solid';
+ className = 'toast--info';
+ }
+
return (
- <Alert
- onDismiss={handleClosePress}
- bsClass={cx(
- 'alert',
- 'toast',
- visible && 'toast--visible',
- toast.toastType === ToastType.SUCCESS && 'toast--success',
- toast.toastType === ToastType.WARNING && 'toast--warning',
- toast.toastType === ToastType.DANGER && 'toast--danger',
- )}
+ <ToastContainer
+ className={cx('alert', 'toast', visible && 'toast--visible', className)}
+ data-test="toast-container"
>
- <ToastContianer>
- {toast.toastType === ToastType.SUCCESS && (
- <Icon name="circle-check-solid" />
- )}
- {toast.toastType === ToastType.WARNING ||
- (toast.toastType === ToastType.DANGER && <Icon name="error-solid" />)}
- <Interweave content={toast.text} />
- </ToastContianer>
- </Alert>
+ <StyledIcon name={iconName} />
+ <Interweave content={toast.text} />
+ <i
+ className="fa fa-close pull-right pointer"
+ role="button"
+ tabIndex={0}
+ onClick={handleClosePress}
+ aria-label="Close"
+ data-test="close-button"
+ />
+ </ToastContainer>
);
}
diff --git a/superset-frontend/src/messageToasts/components/ToastPresenter.tsx b/superset-frontend/src/messageToasts/components/ToastPresenter.tsx
index aba922c..05e3cee 100644
--- a/superset-frontend/src/messageToasts/components/ToastPresenter.tsx
+++ b/superset-frontend/src/messageToasts/components/ToastPresenter.tsx
@@ -25,8 +25,9 @@
max-width: 600px;
position: fixed;
bottom: 0px;
- right: -110px;
- transform: translate(-50%, 0);
+ right: 0px;
+ margin-right: 50px;
+ margin-bottom: 50px;
z-index: ${({ theme }) => theme.zIndex.max};
.toast {
diff --git a/superset-frontend/stylesheets/antd/index.less b/superset-frontend/stylesheets/antd/index.less
index 4b1d66c..cf51b16 100644
--- a/superset-frontend/stylesheets/antd/index.less
+++ b/superset-frontend/stylesheets/antd/index.less
@@ -34,7 +34,6 @@
@processing-color: #66bcfe;
@error-color: #e04355;
@highlight-color: #e04355;
-@warning-color: #fbc700;
@normal-color: #d9d9d9;
@white: #fff;
@black: #000;