You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2021/02/02 06:15:57 UTC

[superset] branch master updated: Migrates Label component from Bootstrap to AntD. (#12774)

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 2adfb85  Migrates Label component from Bootstrap to AntD. (#12774)
2adfb85 is described below

commit 2adfb8559736bff97b47c6fee2725b4344c4ebf3
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Tue Feb 2 03:15:07 2021 -0300

    Migrates Label component from Bootstrap to AntD. (#12774)
---
 .../cypress/integration/explore/control.test.ts    |   2 +-
 .../explore/visualizations/table.test.ts           |   2 +-
 .../spec/javascripts/components/Timer_spec.tsx     |   5 +-
 .../explore/components/RowCountLabel_spec.jsx      |   2 +-
 .../spec/javascripts/profile/Security_spec.tsx     |  12 +-
 .../src/SqlLab/components/QueryStateLabel.jsx      |   6 +-
 .../src/SqlLab/components/QueryTable.jsx           |   2 +-
 .../src/SqlLab/components/SouthPane.jsx            |   7 +-
 .../src/SqlLab/components/SqlEditor.jsx            |   6 +-
 superset-frontend/src/SqlLab/constants.ts          |   2 +-
 .../src/addSlice/AddSliceContainer.tsx             |   2 +-
 superset-frontend/src/common/components/index.tsx  |   1 +
 superset-frontend/src/components/CachedLabel.jsx   |   4 +-
 .../src/components/DatabaseSelector.tsx            |  11 +-
 .../src/components/Label/Label.stories.tsx         |  63 +++----
 .../src/components/Label/Label.test.tsx            |  18 +-
 superset-frontend/src/components/Label/index.tsx   | 209 ++++++++-------------
 superset-frontend/src/components/Timer.tsx         |   9 +-
 .../src/explore/components/RowCountLabel.jsx       |   4 +-
 .../explore/components/controls/VizTypeControl.jsx |  12 +-
 .../src/views/CRUD/chart/ChartCard.tsx             |   2 +-
 21 files changed, 159 insertions(+), 222 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
index 70b0135..c65b5df 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
@@ -104,7 +104,7 @@ describe('VizType control', () => {
       numScripts = nodes.length;
     });
 
-    cy.get('.Control .label').contains('Table').click();
+    cy.get('[data-test="visualization-type"]').contains('Table').click();
 
     cy.get('[role="button"]').contains('Line Chart').click();
 
diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
index 868d2fe..0af7dce 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
@@ -161,7 +161,7 @@ describe('Visualization > Table', () => {
       cy.verifySliceContainer('table');
       expect(response?.body.result[0].data.length).to.eq(limit);
     });
-    cy.get('span.label-danger').contains('10 rows');
+    cy.get('[data-test="row-count-label"]').contains('10 rows');
   });
 
   it('Test table with columns and row limit', () => {
diff --git a/superset-frontend/spec/javascripts/components/Timer_spec.tsx b/superset-frontend/spec/javascripts/components/Timer_spec.tsx
index 4af7ae7..f37004a 100644
--- a/superset-frontend/spec/javascripts/components/Timer_spec.tsx
+++ b/superset-frontend/spec/javascripts/components/Timer_spec.tsx
@@ -21,7 +21,7 @@
  */
 import React from 'react';
 import { render, sleep, waitFor } from 'spec/helpers/testing-library';
-import Timer from 'src/components/Timer';
+import Timer, { TimerProps } from 'src/components/Timer';
 import { now } from 'src/modules/dates';
 
 function parseTime(text?: string | null) {
@@ -29,7 +29,7 @@ function parseTime(text?: string | null) {
 }
 
 describe('Timer', () => {
-  const mockProps = {
+  const mockProps: TimerProps = {
     startTime: now(),
     endTime: undefined,
     isRunning: true,
@@ -41,7 +41,6 @@ describe('Timer', () => {
     const node = screen.getByRole('timer');
     let text = node.textContent || '';
     expect(node).toBeInTheDocument();
-    expect(node).toHaveClass('label-warning');
     expect(node).toHaveTextContent('00:00:00.00');
     // should start running
     await waitFor(() => {
diff --git a/superset-frontend/spec/javascripts/explore/components/RowCountLabel_spec.jsx b/superset-frontend/spec/javascripts/explore/components/RowCountLabel_spec.jsx
index 7776788..13de226 100644
--- a/superset-frontend/spec/javascripts/explore/components/RowCountLabel_spec.jsx
+++ b/superset-frontend/spec/javascripts/explore/components/RowCountLabel_spec.jsx
@@ -45,6 +45,6 @@ describe('RowCountLabel', () => {
       limit: 100,
     };
     const wrapper = shallow(<RowCountLabel {...props} />);
-    expect(wrapper.find(Label).first().props().bsStyle).toBe('danger');
+    expect(wrapper.find(Label).first().props().type).toBe('danger');
   });
 });
diff --git a/superset-frontend/spec/javascripts/profile/Security_spec.tsx b/superset-frontend/spec/javascripts/profile/Security_spec.tsx
index 6732e7b..31eda0a 100644
--- a/superset-frontend/spec/javascripts/profile/Security_spec.tsx
+++ b/superset-frontend/spec/javascripts/profile/Security_spec.tsx
@@ -19,7 +19,7 @@
 import React from 'react';
 import { styledMount as mount } from 'spec/helpers/theming';
 import Security from 'src/profile/components/Security';
-
+import Label from 'src/components/Label';
 import { user, userNoPerms } from './fixtures';
 
 describe('Security', () => {
@@ -31,19 +31,19 @@ describe('Security', () => {
   });
   it('renders 2 role labels', () => {
     const wrapper = mount(<Security {...mockedProps} />);
-    expect(wrapper.find('.roles').find('.label')).toHaveLength(2);
+    expect(wrapper.find('.roles').find(Label)).toHaveLength(2);
   });
   it('renders 2 datasource labels', () => {
     const wrapper = mount(<Security {...mockedProps} />);
-    expect(wrapper.find('.datasources').find('.label')).toHaveLength(2);
+    expect(wrapper.find('.datasources').find(Label)).toHaveLength(2);
   });
   it('renders 3 database labels', () => {
     const wrapper = mount(<Security {...mockedProps} />);
-    expect(wrapper.find('.databases').find('.label')).toHaveLength(3);
+    expect(wrapper.find('.databases').find(Label)).toHaveLength(3);
   });
   it('renders no permission label when empty', () => {
     const wrapper = mount(<Security user={userNoPerms} />);
-    expect(wrapper.find('.datasources').find('.label')).not.toExist();
-    expect(wrapper.find('.databases').find('.label')).not.toExist();
+    expect(wrapper.find('.datasources').find(Label)).not.toExist();
+    expect(wrapper.find('.databases').find(Label)).not.toExist();
   });
 });
diff --git a/superset-frontend/src/SqlLab/components/QueryStateLabel.jsx b/superset-frontend/src/SqlLab/components/QueryStateLabel.jsx
index 74b0d3f..5afb30b 100644
--- a/superset-frontend/src/SqlLab/components/QueryStateLabel.jsx
+++ b/superset-frontend/src/SqlLab/components/QueryStateLabel.jsx
@@ -20,16 +20,16 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import Label from 'src/components/Label';
 
-import { STATE_BSSTYLE_MAP } from '../constants';
+import { STATE_TYPE_MAP } from '../constants';
 
 const propTypes = {
   query: PropTypes.object.isRequired,
 };
 
 export default function QueryStateLabel({ query }) {
-  const bsStyle = STATE_BSSTYLE_MAP[query.state];
+  const type = STATE_TYPE_MAP[query.state];
   return (
-    <Label className="m-r-3" bsStyle={bsStyle}>
+    <Label className="m-r-3" type={type}>
       {query.state}
     </Label>
   );
diff --git a/superset-frontend/src/SqlLab/components/QueryTable.jsx b/superset-frontend/src/SqlLab/components/QueryTable.jsx
index 6342f00..a4235d2 100644
--- a/superset-frontend/src/SqlLab/components/QueryTable.jsx
+++ b/superset-frontend/src/SqlLab/components/QueryTable.jsx
@@ -143,7 +143,7 @@ const QueryTable = props => {
             <ModalTrigger
               className="ResultsModal"
               triggerNode={
-                <Label bsStyle="info" className="pointer">
+                <Label type="info" className="pointer">
                   {t('View results')}
                 </Label>
               }
diff --git a/superset-frontend/src/SqlLab/components/SouthPane.jsx b/superset-frontend/src/SqlLab/components/SouthPane.jsx
index 8abe498..e2ffa0a 100644
--- a/superset-frontend/src/SqlLab/components/SouthPane.jsx
+++ b/superset-frontend/src/SqlLab/components/SouthPane.jsx
@@ -33,7 +33,7 @@ import QueryHistory from './QueryHistory';
 import ResultSet from './ResultSet';
 import {
   STATUS_OPTIONS,
-  STATE_BSSTYLE_MAP,
+  STATE_TYPE_MAP,
   LOCALSTORAGE_MAX_QUERY_AGE_MS,
 } from '../constants';
 
@@ -97,10 +97,7 @@ export class SouthPane extends React.PureComponent {
   render() {
     if (this.props.offline) {
       return (
-        <Label
-          className="m-r-3"
-          bsStyle={STATE_BSSTYLE_MAP[STATUS_OPTIONS.offline]}
-        >
+        <Label className="m-r-3" type={STATE_TYPE_MAP[STATUS_OPTIONS.offline]}>
           {STATUS_OPTIONS.offline}
         </Label>
       );
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx b/superset-frontend/src/SqlLab/components/SqlEditor.jsx
index 34bcc2d..ee89a0f 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor.jsx
@@ -71,7 +71,7 @@ import ShareSqlLabQuery from './ShareSqlLabQuery';
 import SqlEditorLeftBar from './SqlEditorLeftBar';
 import AceEditorWrapper from './AceEditorWrapper';
 import {
-  STATE_BSSTYLE_MAP,
+  STATE_TYPE_MAP,
   SQL_EDITOR_GUTTER_HEIGHT,
   SQL_EDITOR_GUTTER_MARGIN,
   SQL_TOOLBAR_HEIGHT,
@@ -575,7 +575,7 @@ class SqlEditor extends React.PureComponent {
             this.props.latestQuery.rows,
           )}
         >
-          <Label bsStyle="warning">LIMIT</Label>
+          <Label type="warning">LIMIT</Label>
         </Tooltip>
       );
     }
@@ -670,7 +670,7 @@ class SqlEditor extends React.PureComponent {
               <Timer
                 startTime={this.props.latestQuery.startDttm}
                 endTime={this.props.latestQuery.endDttm}
-                state={STATE_BSSTYLE_MAP[this.props.latestQuery.state]}
+                state={STATE_TYPE_MAP[this.props.latestQuery.state]}
                 isRunning={this.props.latestQuery.state === 'running'}
               />
             )}
diff --git a/superset-frontend/src/SqlLab/constants.ts b/superset-frontend/src/SqlLab/constants.ts
index e191ab9..7d0ea09 100644
--- a/superset-frontend/src/SqlLab/constants.ts
+++ b/superset-frontend/src/SqlLab/constants.ts
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export const STATE_BSSTYLE_MAP = {
+export const STATE_TYPE_MAP = {
   offline: 'danger',
   failed: 'danger',
   pending: 'info',
diff --git a/superset-frontend/src/addSlice/AddSliceContainer.tsx b/superset-frontend/src/addSlice/AddSliceContainer.tsx
index 0469461..9e99c20 100644
--- a/superset-frontend/src/addSlice/AddSliceContainer.tsx
+++ b/superset-frontend/src/addSlice/AddSliceContainer.tsx
@@ -134,7 +134,7 @@ export default class AddSliceContainer extends React.PureComponent<
                 name="select-vis-type"
                 onChange={this.changeVisType}
                 value={this.state.visType}
-                labelBsStyle="primary"
+                labelType="primary"
               />
             </div>
             <br />
diff --git a/superset-frontend/src/common/components/index.tsx b/superset-frontend/src/common/components/index.tsx
index f56b09e..bfbccbe 100644
--- a/superset-frontend/src/common/components/index.tsx
+++ b/superset-frontend/src/common/components/index.tsx
@@ -52,6 +52,7 @@ export {
   Select,
   Skeleton,
   Switch,
+  Tag,
   Tabs,
   Tooltip,
   Input as AntdInput,
diff --git a/superset-frontend/src/components/CachedLabel.jsx b/superset-frontend/src/components/CachedLabel.jsx
index db4bd2e..a7bc720 100644
--- a/superset-frontend/src/components/CachedLabel.jsx
+++ b/superset-frontend/src/components/CachedLabel.jsx
@@ -67,12 +67,12 @@ class CacheLabel extends React.PureComponent {
   }
 
   render() {
-    const labelStyle = this.state.hovered ? 'primary' : 'default';
+    const labelType = this.state.hovered ? 'primary' : 'default';
     return (
       <TooltipWrapper tooltip={this.state.tooltipContent} label="cache-desc">
         <Label
           className={`${this.props.className}`}
-          bsStyle={labelStyle}
+          type={labelType}
           onClick={this.props.onClick}
           onMouseOver={this.mouseOver.bind(this)}
           onMouseOut={this.mouseOut.bind(this)}
diff --git a/superset-frontend/src/components/DatabaseSelector.tsx b/superset-frontend/src/components/DatabaseSelector.tsx
index 0d565f9..4cc2e73 100644
--- a/superset-frontend/src/components/DatabaseSelector.tsx
+++ b/superset-frontend/src/components/DatabaseSelector.tsx
@@ -54,6 +54,11 @@ const DatabaseSelectorWrapper = styled.div`
   }
 `;
 
+const DatabaseOption = styled.span`
+  display: inline-flex;
+  align-items: center;
+`;
+
 interface DatabaseSelectorProps {
   dbId: number;
   formMode?: boolean;
@@ -184,9 +189,9 @@ export default function DatabaseSelector({
 
   function renderDatabaseOption(db: any) {
     return (
-      <span title={db.database_name}>
-        <Label bsStyle="default">{db.backend}</Label> {db.database_name}
-      </span>
+      <DatabaseOption title={db.database_name}>
+        <Label type="default">{db.backend}</Label> {db.database_name}
+      </DatabaseOption>
     );
   }
 
diff --git a/superset-frontend/src/components/Label/Label.stories.tsx b/superset-frontend/src/components/Label/Label.stories.tsx
index fe5dfdb..07fc7c0 100644
--- a/superset-frontend/src/components/Label/Label.stories.tsx
+++ b/superset-frontend/src/components/Label/Label.stories.tsx
@@ -18,59 +18,52 @@
  */
 import React from 'react';
 import { action } from '@storybook/addon-actions';
-import { withKnobs, select, boolean, text } from '@storybook/addon-knobs';
-import Label from './index';
+import Label, { Type } from './index';
 
 export default {
   title: 'Label',
   component: Label,
-  decorators: [withKnobs],
-  excludeStories: /.*Knob$/,
+  excludeStories: 'options',
 };
 
-export const bsStyleKnob = {
-  label: 'Types',
-  options: {
-    default: 'default',
-    info: 'info',
-    success: 'success',
-    warning: 'warning',
-    danger: 'danger',
-    secondary: 'secondary',
-    primary: 'primary',
-  },
-  defaultValue: 'default',
-};
+export const options = [
+  'default',
+  'info',
+  'success',
+  'warning',
+  'danger',
+  'primary',
+  'secondary',
+];
 
 export const LabelGallery = () => (
   <>
     <h4>Non-interactive</h4>
-    {Object.values(bsStyleKnob.options).map(opt => (
-      <Label key={opt} bsStyle={opt}>
+    {Object.values(options).map((opt: Type) => (
+      <Label key={opt} type={opt}>
         {`style: "${opt}"`}
       </Label>
     ))}
     <br />
     <h4>Interactive</h4>
-    {Object.values(bsStyleKnob.options).map(opt => (
-      <Label key={opt} bsStyle={opt} onClick={action('clicked')}>
+    {Object.values(options).map((opt: Type) => (
+      <Label key={opt} type={opt} onClick={action('clicked')}>
         {`style: "${opt}"`}
       </Label>
     ))}
   </>
 );
 
-export const InteractiveLabel = () => (
-  <Label
-    bsStyle={select(
-      bsStyleKnob.label,
-      bsStyleKnob.options,
-      bsStyleKnob.defaultValue,
-    )}
-    onClick={
-      boolean('Has onClick action', false) ? action('clicked') : undefined
-    }
-  >
-    {text('Label', 'Label!')}
-  </Label>
-);
+export const InteractiveLabel = (args: any) => {
+  const { hasOnClick, label, ...rest } = args;
+  return (
+    <Label onClick={hasOnClick ? action('clicked') : undefined} {...rest}>
+      {label}
+    </Label>
+  );
+};
+
+InteractiveLabel.args = {
+  hasOnClick: true,
+  label: 'Example',
+};
diff --git a/superset-frontend/src/components/Label/Label.test.tsx b/superset-frontend/src/components/Label/Label.test.tsx
index 1535d4c..d1879d0 100644
--- a/superset-frontend/src/components/Label/Label.test.tsx
+++ b/superset-frontend/src/components/Label/Label.test.tsx
@@ -21,7 +21,7 @@ import React from 'react';
 import { ReactWrapper } from 'enzyme';
 import { styledMount as mount } from 'spec/helpers/theming';
 import Label from '.';
-import { LabelGallery, bsStyleKnob } from './Label.stories';
+import { LabelGallery, options } from './Label.stories';
 
 describe('Label', () => {
   let wrapper: ReactWrapper;
@@ -34,23 +34,13 @@ describe('Label', () => {
   it('works with an onClick handler', () => {
     const mockAction = jest.fn();
     wrapper = mount(<Label onClick={mockAction} />);
-    wrapper.find('.label').simulate('click');
+    wrapper.find(Label).simulate('click');
     expect(mockAction).toHaveBeenCalled();
   });
 
   // test stories from the storybook!
-  it('renders all the sorybook gallery variants', () => {
+  it('renders all the storybook gallery variants', () => {
     wrapper = mount(<LabelGallery />);
-    Object.values(bsStyleKnob.options).forEach(opt => {
-      expect(wrapper.find(`.label-${opt}`).at(0).text()).toEqual(
-        `style: "${opt}"`,
-      );
-    });
-  });
-
-  // test things NOT in the storybook!
-  it('renders custom label styles without melting', () => {
-    wrapper = mount(<Label bsStyle="foobar" />);
-    expect(wrapper.find('Label.label-foobar')).toHaveLength(1);
+    expect(wrapper.find(Label).length).toEqual(options.length * 2);
   });
 });
diff --git a/superset-frontend/src/components/Label/index.tsx b/superset-frontend/src/components/Label/index.tsx
index 18bd1d2..781e15f 100644
--- a/superset-frontend/src/components/Label/index.tsx
+++ b/superset-frontend/src/components/Label/index.tsx
@@ -17,149 +17,98 @@
  * under the License.
  */
 import React, { CSSProperties } from 'react';
-import { Label as BootstrapLabel } from 'react-bootstrap';
-import { styled } from '@superset-ui/core';
-import cx from 'classnames';
+import { Tag } from 'src/common/components';
+import { useTheme } from '@superset-ui/core';
 
-export type OnClickHandler = React.MouseEventHandler<BootstrapLabel>;
+export type OnClickHandler = React.MouseEventHandler<HTMLElement>;
+
+export type Type =
+  | 'success'
+  | 'warning'
+  | 'danger'
+  | 'info'
+  | 'default'
+  | 'primary'
+  | 'secondary';
 
 export interface LabelProps {
   key?: string;
   className?: string;
-  id?: string;
-  tooltip?: string;
-  placement?: string;
   onClick?: OnClickHandler;
-  bsStyle?: string;
+  type?: Type;
   style?: CSSProperties;
   children?: React.ReactNode;
   role?: string;
 }
 
-const SupersetLabel = styled(BootstrapLabel)`
-  /* un-bunch them! */
-  margin-right: ${({ theme }) => theme.gridUnit}px;
-  max-width: 100%;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  vertical-align: middle;
+export default function Label(props: LabelProps) {
+  const theme = useTheme();
+  const { colors, transitionTiming } = theme;
+  const { type, onClick, children, ...rest } = props;
+  const {
+    primary,
+    secondary,
+    grayscale,
+    success,
+    warning,
+    error,
+    info,
+  } = colors;
 
-  &:first-of-type {
-    margin-left: 0;
-  }
-  &:last-of-type {
-    margin-right: 0;
-  }
-  display: inline-block;
-  border-width: 1px;
-  border-style: solid;
-  cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
-  transition: background-color ${({ theme }) => theme.transitionTiming}s;
-  &.label-warning {
-    background-color: ${({ theme }) => theme.colors.warning.base};
-    border-color: ${({ theme, onClick }) =>
-      onClick ? theme.colors.warning.dark1 : 'transparent'};
-    &:hover {
-      background-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.warning.dark1 : theme.colors.warning.base};
-      border-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.warning.dark2 : 'transparent'};
-    }
-  }
-  &.label-danger {
-    background-color: ${({ theme }) => theme.colors.error.base};
-    border-color: ${({ theme, onClick }) =>
-      onClick ? theme.colors.error.dark1 : 'transparent'};
-    &:hover {
-      background-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.error.dark1 : theme.colors.error.base};
-      border-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.error.dark2 : 'transparent'};
-    }
-  }
-  &.label-success {
-    background-color: ${({ theme }) => theme.colors.success.base};
-    border-color: ${({ theme, onClick }) =>
-      onClick ? theme.colors.success.dark1 : 'transparent'};
-    &:hover {
-      background-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.success.dark1 : theme.colors.success.base};
-      border-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.success.dark2 : 'transparent'};
-    }
-  }
-  &.label-default {
-    background-color: ${({ theme }) => theme.colors.grayscale.light3};
-    color: ${({ theme }) => theme.colors.grayscale.dark1};
-    border-color: ${({ theme, onClick }) =>
-      onClick ? theme.colors.grayscale.light2 : 'transparent'};
-    &:hover {
-      background-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.primary.light2 : theme.colors.grayscale.light3};
-      border-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.primary.light1 : 'transparent'};
-    }
-  }
-  &.label-info {
-    background-color: ${({ theme }) => theme.colors.info};
-    border-color: ${({ theme, onClick }) =>
-      onClick ? theme.colors.info.dark1 : 'transparent'};
-    &:hover {
-      background-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.info.dark1 : theme.colors.info.base};
-      border-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.info.dark2 : 'transparent'};
-    }
-  }
-  &.label-primary {
-    background-color: ${({ theme }) => theme.colors.primary.base};
-    border-color: ${({ theme, onClick }) =>
-      onClick ? theme.colors.primary.dark1 : 'transparent'};
-    &:hover {
-      background-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.primary.dark2 : theme.colors.primary.base};
-      border-color: ${({ theme, onClick }) =>
-        onClick
-          ? theme.colors.primary.dark2
-          : 'transparent'}; /* would be nice if we had a darker color, but that's the floor! */
-    }
-  }
-  /* note this is NOT a supported bootstrap label Style... this overrides default */
-  &.label-secondary {
-    background-color: ${({ theme }) => theme.colors.secondary.base};
-    color: ${({ theme }) => theme.colors.grayscale.light4};
-    border-color: ${({ theme, onClick }) =>
-      onClick ? theme.colors.secondary.dark1 : 'transparent'};
-    &:hover {
-      background-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.secondary.dark1 : theme.colors.secondary.base};
-      border-color: ${({ theme, onClick }) =>
-        onClick ? theme.colors.secondary.dark2 : 'transparent'};
+  let backgroundColor = grayscale.light3;
+  let backgroundColorHover = onClick ? primary.light2 : grayscale.light3;
+  let borderColor = onClick ? grayscale.light2 : 'transparent';
+  let borderColorHover = onClick ? primary.light1 : 'transparent';
+  let color = grayscale.dark1;
+
+  if (type && type !== 'default') {
+    color = grayscale.light4;
+
+    let baseColor;
+    if (type === 'success') {
+      baseColor = success;
+    } else if (type === 'warning') {
+      baseColor = warning;
+    } else if (type === 'danger') {
+      baseColor = error;
+    } else if (type === 'info') {
+      baseColor = info;
+    } else if (type === 'secondary') {
+      baseColor = secondary;
+    } else {
+      baseColor = primary;
     }
+
+    backgroundColor = baseColor.base;
+    backgroundColorHover = onClick ? baseColor.dark1 : baseColor.base;
+    borderColor = onClick ? baseColor.dark1 : 'transparent';
+    borderColorHover = onClick ? baseColor.dark2 : 'transparent';
   }
-`;
 
-export default function Label(props: LabelProps) {
-  const officialBootstrapStyles = [
-    'success',
-    'warning',
-    'danger',
-    'info',
-    'default',
-    'primary',
-  ];
-  const labelProps = {
-    ...props,
-    placement: props.placement || 'top',
-    bsStyle: officialBootstrapStyles.includes(props.bsStyle || '')
-      ? props.bsStyle
-      : 'default',
-    className: cx(props.className, {
-      [`label-${props.bsStyle}`]: !officialBootstrapStyles.includes(
-        props.bsStyle || '',
-      ),
-    }),
-  };
-  return <SupersetLabel {...labelProps}>{props.children}</SupersetLabel>;
+  return (
+    <Tag
+      css={{
+        transition: `background-color ${transitionTiming}s`,
+        whiteSpace: 'nowrap',
+        cursor: onClick ? 'pointer' : 'default',
+        overflow: 'hidden',
+        textOverflow: 'ellipsis',
+        backgroundColor,
+        borderColor,
+        borderRadius: 21,
+        padding: '0.35em 0.8em',
+        lineHeight: 1,
+        color,
+        '&:hover': {
+          backgroundColor: backgroundColorHover,
+          borderColor: borderColorHover,
+          opacity: 1,
+        },
+      }}
+      onClick={onClick}
+      {...rest}
+    >
+      {children}
+    </Tag>
+  );
 }
diff --git a/superset-frontend/src/components/Timer.tsx b/superset-frontend/src/components/Timer.tsx
index 3b87755..612c78d 100644
--- a/superset-frontend/src/components/Timer.tsx
+++ b/superset-frontend/src/components/Timer.tsx
@@ -18,19 +18,18 @@
  */
 import React, { useEffect, useRef, useState } from 'react';
 import { styled } from '@superset-ui/core';
-import Label from 'src/components/Label';
+import Label, { Type } from 'src/components/Label';
 
 import { now, fDuration } from 'src/modules/dates';
 
-interface TimerProps {
+export interface TimerProps {
   endTime?: number;
   isRunning: boolean;
   startTime?: number;
-  status?: string;
+  status?: Type;
 }
 
 const TimerLabel = styled(Label)`
-  line-height: 13px;
   text-align: left;
   width: 91px;
 `;
@@ -69,7 +68,7 @@ export default function Timer({
   }, [endTime, isRunning, startTime]);
 
   return (
-    <TimerLabel bsStyle={status} role="timer">
+    <TimerLabel type={status} role="timer">
       {clockStr}
     </TimerLabel>
   );
diff --git a/superset-frontend/src/explore/components/RowCountLabel.jsx b/superset-frontend/src/explore/components/RowCountLabel.jsx
index 8fce6f8..6bd38ba 100644
--- a/superset-frontend/src/explore/components/RowCountLabel.jsx
+++ b/superset-frontend/src/explore/components/RowCountLabel.jsx
@@ -37,7 +37,7 @@ const defaultProps = {
 
 export default function RowCountLabel({ rowcount, limit, suffix, loading }) {
   const limitReached = rowcount === limit;
-  const bsStyle =
+  const type =
     limitReached || (rowcount === 0 && !loading) ? 'danger' : 'default';
   const formattedRowCount = getNumberFormatter()(rowcount);
   const tooltip = (
@@ -48,7 +48,7 @@ export default function RowCountLabel({ rowcount, limit, suffix, loading }) {
   );
   return (
     <TooltipWrapper label="tt-rowcount" tooltip={tooltip}>
-      <Label bsStyle={bsStyle}>
+      <Label type={type} data-test="row-count-label">
         {loading ? 'Loading...' : `${formattedRowCount} ${suffix}`}
       </Label>
     </TooltipWrapper>
diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl.jsx b/superset-frontend/src/explore/components/controls/VizTypeControl.jsx
index c24c0c3..1f82080 100644
--- a/superset-frontend/src/explore/components/controls/VizTypeControl.jsx
+++ b/superset-frontend/src/explore/components/controls/VizTypeControl.jsx
@@ -34,12 +34,12 @@ const propTypes = {
   name: PropTypes.string.isRequired,
   onChange: PropTypes.func,
   value: PropTypes.string.isRequired,
-  labelBsStyle: PropTypes.string,
+  labelType: PropTypes.string,
 };
 
 const defaultProps = {
   onChange: () => {},
-  labelBsStyle: 'default',
+  labelType: 'default',
 };
 
 const registry = getChartMetadataRegistry();
@@ -162,7 +162,7 @@ const VizTypeControl = props => {
     );
   };
 
-  const { value, labelBsStyle } = props;
+  const { value, labelType } = props;
   const filterString = filter.toLowerCase();
 
   const filteredTypes = DEFAULT_ORDER.filter(type => registry.has(type))
@@ -201,7 +201,11 @@ const VizTypeControl = props => {
         title={t('Click to change visualization type')}
       >
         <>
-          <Label onClick={toggleModal} bsStyle={labelBsStyle}>
+          <Label
+            onClick={toggleModal}
+            type={labelType}
+            data-test="visualization-type"
+          >
             {registry.has(value) ? registry.get(value).name : `${value}`}
           </Label>
           <VizSupportValidation vizType={value} />
diff --git a/superset-frontend/src/views/CRUD/chart/ChartCard.tsx b/superset-frontend/src/views/CRUD/chart/ChartCard.tsx
index c6be62a..a87da37 100644
--- a/superset-frontend/src/views/CRUD/chart/ChartCard.tsx
+++ b/superset-frontend/src/views/CRUD/chart/ChartCard.tsx
@@ -137,7 +137,7 @@ export default function ChartCard({
         description={t('Last modified %s', chart.changed_on_delta_humanized)}
         coverLeft={<FacePile users={chart.owners || []} />}
         coverRight={
-          <Label bsStyle="secondary">{chart.datasource_name_text}</Label>
+          <Label type="secondary">{chart.datasource_name_text}</Label>
         }
         actions={
           <ListViewCard.Actions