diff --git a/superset-frontend/src/SqlLab/components/RunQueryActionButton.tsx b/superset-frontend/src/SqlLab/components/RunQueryActionButton.tsx
index f0c1e99e68e29..2297f8ff684d8 100644
--- a/superset-frontend/src/SqlLab/components/RunQueryActionButton.tsx
+++ b/superset-frontend/src/SqlLab/components/RunQueryActionButton.tsx
@@ -17,75 +17,110 @@
* under the License.
*/
import React from 'react';
-import { t } from '@superset-ui/core';
+import { t, styled, supersetTheme } from '@superset-ui/core';
-import { Dropdown, Menu } from 'src/common/components';
+import { Menu } from 'src/common/components';
+import Button, { ButtonProps } from 'src/components/Button';
import Icon from 'src/components/Icon';
-
-const NO_OP = () => undefined;
+import { DropdownButton, DropdownProps } from 'src/common/components/Dropdown';
interface Props {
allowAsync: boolean;
- dbId?: number;
queryState?: string;
runQuery: (c?: boolean) => void;
selectedText?: string;
stopQuery: () => void;
sql: string;
- overlayCreateAsMenu: typeof Menu;
+ overlayCreateAsMenu: typeof Menu | null;
}
+type QueryButtonProps = DropdownProps | ButtonProps;
+
+const buildText = (
+ shouldShowStopButton: boolean,
+ selectedText: string | undefined,
+): string | JSX.Element => {
+ if (shouldShowStopButton) {
+ return (
+ <>
+ {t('Stop')}
+ >
+ );
+ }
+ if (selectedText) {
+ return t('Run Selection');
+ }
+ return t('Run');
+};
+
+const onClick = (
+ shouldShowStopButton: boolean,
+ allowAsync: boolean,
+ runQuery: (c?: boolean) => void = () => undefined,
+ stopQuery = () => {},
+): void => {
+ if (shouldShowStopButton) return stopQuery();
+ if (allowAsync) {
+ return runQuery(true);
+ }
+ return runQuery(false);
+};
+
+const StyledButton = styled.span`
+ button {
+ line-height: 13px;
+ &:last-of-type {
+ margin-right: ${({ theme }) => theme.gridUnit * 2}px;
+ }
+ }
+`;
+
const RunQueryActionButton = ({
allowAsync = false,
- dbId,
queryState,
- runQuery = NO_OP,
selectedText,
- stopQuery = NO_OP,
sql = '',
overlayCreateAsMenu,
+ runQuery,
+ stopQuery,
}: Props) => {
- const runBtnText = selectedText ? t('Run Selection') : t('Run');
const btnStyle = selectedText ? 'warning' : 'primary';
const shouldShowStopBtn =
!!queryState && ['running', 'pending'].indexOf(queryState) > -1;
- if (shouldShowStopBtn) {
- return (
- }
- type={btnStyle}
- overlay={overlayCreateAsMenu}
- disabled={!sql.trim()}
- >
- {t('Stop')}
-
- );
- }
- if (allowAsync) {
- return (
- runQuery(true)}
- icon={}
- type={btnStyle}
- overlay={overlayCreateAsMenu}
+ const ButtonComponent: React.FC = overlayCreateAsMenu
+ ? (DropdownButton as React.FC)
+ : Button;
+
+ return (
+
+
+ onClick(shouldShowStopBtn, allowAsync, runQuery, stopQuery)
+ }
disabled={!sql.trim()}
+ buttonSize="small"
+ tooltip={
+ shouldShowStopBtn
+ ? t('Stop running (Ctrl + x)')
+ : t('Run query (Ctrl + Return)')
+ }
+ cta
+ {...(overlayCreateAsMenu
+ ? {
+ overlay: overlayCreateAsMenu,
+ icon: (
+
+ ),
+ }
+ : { buttonStyle: btnStyle })}
>
- {runBtnText}
-
- );
- }
- return (
- runQuery(false)}
- icon={}
- type={btnStyle}
- overlay={overlayCreateAsMenu}
- disabled={!sql.trim()}
- >
- {runBtnText}
-
+ {buildText(shouldShowStopBtn, selectedText)}
+
+
);
};
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx b/superset-frontend/src/SqlLab/components/SqlEditor.jsx
index 0f465ff612888..6ae49763660f9 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor.jsx
@@ -25,7 +25,7 @@ import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import { Form } from 'react-bootstrap';
import Split from 'react-split';
-import { t, styled } from '@superset-ui/core';
+import { t, styled, supersetTheme } from '@superset-ui/core';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import StyledModal from 'src/common/components/Modal';
@@ -88,8 +88,46 @@ const WINDOW_RESIZE_THROTTLE_MS = 100;
const LimitSelectStyled = styled.span`
.ant-dropdown-trigger {
+ align-items: center;
color: black;
+ display: flex;
+ font-size: 12px;
+ margin-right: ${({ theme }) => theme.gridUnit * 2}px;
text-decoration: none;
+ span {
+ display: inline-block;
+ margin-right: ${({ theme }) => theme.gridUnit * 2}px;
+ &:last-of-type: {
+ margin-right: ${({ theme }) => theme.gridUnit * 4}px;
+ }
+ }
+ }
+`;
+
+const StyledToolbar = styled.div`
+ padding: 10px 10px 5px 10px;
+ background-color: @lightest;
+ display: flex;
+ justify-content: space-between;
+ border: 1px solid ${supersetTheme.colors.grayscale.light2};
+ border-top: 0;
+
+ form {
+ margin-block-end: 0;
+ }
+
+ .leftItems form,
+ .rightItems {
+ display: flex;
+ align-items: center;
+ & > span {
+ margin-right: ${({ theme }) => theme.gridUnit * 2}px;
+ display: inline-block;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
}
`;
@@ -100,7 +138,7 @@ const propTypes = {
tables: PropTypes.array.isRequired,
editorQueries: PropTypes.array.isRequired,
dataPreviewQueries: PropTypes.array.isRequired,
- queryEditorId: PropTypes.number.isRequired,
+ queryEditorId: PropTypes.string.isRequired,
hideLeftBar: PropTypes.bool,
defaultQueryLimit: PropTypes.number.isRequired,
maxRow: PropTypes.number.isRequired,
@@ -534,10 +572,8 @@ class SqlEditor extends React.PureComponent {
}
// eslint-disable-next-line camelcase
- const {
- allow_ctas: allowCTAS,
- allow_cvas: allowCVAS,
- } = this.props.database;
+ const { allow_ctas: allowCTAS, allow_cvas: allowCVAS } =
+ this.props.database || {};
const showMenu = allowCTAS || allowCVAS;
const runMenuBtn = (
@@ -572,7 +608,7 @@ class SqlEditor extends React.PureComponent {
);
return (
-
+
);
}
diff --git a/superset-frontend/src/SqlLab/main.less b/superset-frontend/src/SqlLab/main.less
index 7123ed033f9fd..57be0412d7075 100644
--- a/superset-frontend/src/SqlLab/main.less
+++ b/superset-frontend/src/SqlLab/main.less
@@ -132,33 +132,6 @@ div.Workspace {
padding-top: 10px;
}
-.sql-toolbar {
- padding: 10px 10px 5px 10px;
- background-color: @lightest;
- display: flex;
- justify-content: space-between;
- border: 1px solid @gray-light;
- border-top: 0;
-
- form {
- margin-block-end: 0;
- }
-
- .leftItems form,
- .rightItems {
- & > span {
- margin-right: 5px;
- margin-bottom: 5px;
- display: inline-block;
-
- &:last-child {
- margin-right: 0;
- font-feature-settings: 'kern' 1, 'tnum' 1;
- }
- }
- }
-}
-
.no-shadow {
box-shadow: none;
background-color: transparent;
diff --git a/superset-frontend/src/common/components/Dropdown.tsx b/superset-frontend/src/common/components/Dropdown.tsx
index 47733faebfcf0..1b9e13cadbc87 100644
--- a/superset-frontend/src/common/components/Dropdown.tsx
+++ b/superset-frontend/src/common/components/Dropdown.tsx
@@ -17,8 +17,9 @@
* under the License.
*/
import React from 'react';
-import { Dropdown as AntdDropdown } from 'src/common/components';
+import { Dropdown as AntdDropdown, Tooltip } from 'src/common/components';
import { styled } from '@superset-ui/core';
+import kebabCase from 'lodash/kebabCase';
const MenuDots = styled.div`
width: ${({ theme }) => theme.gridUnit * 0.75}px;
@@ -65,8 +66,47 @@ const MenuDotsWrapper = styled.div`
padding-left: ${({ theme }) => theme.gridUnit}px;
`;
-interface DropdownProps {
+const StyledDropdownButton = styled.div`
+ .ant-btn-group {
+ button.ant-btn {
+ background-color: ${({ theme }) => theme.colors.primary.dark1};
+ border-color: transparent;
+ color: ${({ theme }) => theme.colors.grayscale.light5};
+ font-size: 12px;
+ line-height: 13px;
+ outline: none;
+ text-transform: uppercase;
+ &:first-of-type {
+ border-radius: ${({ theme }) =>
+ `${theme.gridUnit}px 0 0 ${theme.gridUnit}px`};
+ margin-right: 1px;
+ width: 108px;
+ }
+ &:last-of-type {
+ border-radius: ${({ theme }) =>
+ `0 ${theme.gridUnit}px ${theme.gridUnit}px 0`};
+ margin-right: ${({ theme }) => theme.gridUnit * 2}px;
+ width: ${({ theme }) => theme.gridUnit * 9}px;
+ &:before,
+ &:hover:before {
+ border-left: 1px solid ${({ theme }) => theme.colors.grayscale.light5};
+ content: '';
+ display: block;
+ height: 23px;
+ margin: 0;
+ position: absolute;
+ top: ${({ theme }) => theme.gridUnit * 0.75}px;
+ width: ${({ theme }) => theme.gridUnit * 0.25}px;
+ }
+ }
+ }
+ }
+`;
+
+export interface DropdownProps {
overlay: React.ReactElement;
+ tooltip?: string;
+ placement?: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
}
export const Dropdown = ({ overlay, ...rest }: DropdownProps) => (
@@ -76,3 +116,28 @@ export const Dropdown = ({ overlay, ...rest }: DropdownProps) => (
);
+
+export const DropdownButton = ({
+ overlay,
+ tooltip,
+ placement,
+ ...rest
+}: DropdownProps) => {
+ const button = (
+
+
+
+ );
+ if (tooltip) {
+ return (
+
+ {button}
+
+ );
+ }
+ return { button };
+};
diff --git a/superset-frontend/src/common/components/index.tsx b/superset-frontend/src/common/components/index.tsx
index 9d246f6b3274b..d613c8c0e2d7b 100644
--- a/superset-frontend/src/common/components/index.tsx
+++ b/superset-frontend/src/common/components/index.tsx
@@ -20,7 +20,7 @@ import React from 'react';
import { styled } from '@superset-ui/core';
// eslint-disable-next-line no-restricted-imports
import { Menu as AntdMenu, Dropdown, Skeleton } from 'antd';
-import { DropDownProps } from 'antd/lib/dropdown';
+import { DropDownProps as AntdDropdownProps } from 'antd/lib/dropdown';
/*
Antd is re-exported from here so we can override components with Emotion as needed.
@@ -78,7 +78,7 @@ export const Menu = Object.assign(AntdMenu, {
Item: MenuItem,
});
-export const NoAnimationDropdown = (props: DropDownProps) => (
+export const NoAnimationDropdown = (props: AntdDropdownProps) => (