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')} &nbsp;
-            <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')} &nbsp;
+                <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;