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

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


The following commit(s) were added to refs/heads/master by this push:
     new 16459ad  style: DOCTYPE tag, and related CSS cleanup/refactoring 
(#10302)
16459ad is described below

commit 16459ad401d916d0ce7ca6ffa6775fdcb8a37ae8
Author: Evan Rusackas <e...@preset.io>
AuthorDate: Wed Jul 29 18:49:32 2020 -0700

    style: DOCTYPE tag, and related CSS cleanup/refactoring (#10302)
---
 .../integration/explore/AdhocMetrics.test.ts       |  55 ++++++------
 .../cypress/integration/explore/advanced.test.ts   |  18 ++--
 .../cypress/integration/explore/control.test.ts    |  12 +--
 .../cypress/integration/explore/filter_box.test.js |   4 +-
 .../explore/visualizations/line.test.ts            |   4 +-
 .../src/components/ListView/ListView.tsx           |   8 ++
 .../explore/components/AdhocMetricEditPopover.jsx  |   8 +-
 .../components/AdhocMetricEditPopoverTitle.jsx     |   6 +-
 .../explore/components/ControlPanelsContainer.jsx  |  69 ++++++++------
 .../src/explore/components/ExploreChartPanel.jsx   |  26 ++++--
 .../explore/components/ExploreViewContainer.jsx    | 100 +++++++++------------
 .../src/explore/components/QueryAndSaveBtns.css    |  21 -----
 .../src/explore/components/QueryAndSaveBtns.jsx    |  88 ++++++++++++------
 superset-frontend/stylesheets/superset.less        |   3 +
 superset/templates/superset/basic.html             |   2 +-
 superset/templates/superset/paper-theme.html       |   2 +-
 .../templates/superset/reports/slice_data.html     |   1 +
 superset/templates/superset/theme.html             |   8 +-
 superset/templates/superset/traceback.html         |   7 +-
 19 files changed, 247 insertions(+), 195 deletions(-)

diff --git 
a/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts
 
b/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts
index ef7dc9b..59722a0 100644
--- 
a/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts
+++ 
b/superset-frontend/cypress-base/cypress/integration/explore/AdhocMetrics.test.ts
@@ -26,25 +26,27 @@ describe('AdhocMetrics', () => {
 
   it('Clear metric and set simple adhoc metric', () => {
     const metric = 'sum(sum_girls)';
-    const metricName = 'Girl Births';
+    const metricName = 'Sum Girls';
 
     cy.visitChartByName('Num Births Trend');
     cy.verifySliceSuccess({ waitAlias: '@postJson' });
 
-    cy.get('[data-test=metrics]').within(() => {
-      cy.get('.Select__clear-indicator').click();
-      cy.get('.Select__control input').type('sum_girls');
-      cy.get('.Select__option--is-focused').trigger('mousedown').click();
-    });
+    cy.get('[data-test=metrics]').find('.Select__clear-indicator').click();
 
-    cy.get('#metrics-edit-popover').within(() => {
-      cy.get('.popover-title').within(() => {
-        cy.get('span').click();
-        cy.get('input').type(metricName);
-      });
-      cy.get('button').contains('Save').click();
-    });
-    cy.get('.Select__multi-value__label').contains(metricName);
+    cy.get('[data-test=metrics]')
+      .find('.Select__control input')
+      .type('sum_girls', { force: true });
+
+    cy.get('[data-test=metrics]')
+      .find('.Select__option--is-focused')
+      .trigger('mousedown')
+      .click();
+
+    cy.get('[data-test="AdhocMetricEditTitle#trigger"]').click();
+    cy.get('[data-test="AdhocMetricEditTitle#input"]').type(metricName);
+    cy.get('[data-test="AdhocMetricEdit#save"]').contains('Save').click();
+
+    cy.get('.metrics-select .metric-option').contains(metricName);
 
     cy.get('button.query').click();
     cy.verifySliceSuccess({
@@ -59,20 +61,21 @@ describe('AdhocMetrics', () => {
     cy.verifySliceSuccess({ waitAlias: '@postJson' });
 
     // select column "num"
-    cy.get('[data-test=metrics]').within(() => {
-      cy.get('.Select__clear-indicator').click();
-      cy.get('.Select__control').click();
-      cy.get('.Select__control input').type('num');
-      cy.get('.option-label').contains(/^num$/).click();
-    });
+    cy.get('[data-test=metrics]').find('.Select__clear-indicator').click();
+
+    cy.get('[data-test=metrics]').find('.Select__control').click();
+
+    cy.get('[data-test=metrics]').find('.Select__control input').type('num');
+
+    cy.get('[data-test=metrics]').find('.option-label').last().click();
 
     // add custom SQL
-    cy.get('#metrics-edit-popover').within(() => {
-      cy.get('#adhoc-metric-edit-tabs-tab-SQL').click();
-      cy.get('.ace_content').click();
-      cy.get('.ace_text-input').type('/COUNT(DISTINCT name)', { force: true });
-      cy.get('button').contains('Save').click();
-    });
+    cy.get('#adhoc-metric-edit-tabs-tab-SQL').click();
+    cy.get('#metrics-edit-popover').find('.ace_content').click();
+    cy.get('#metrics-edit-popover')
+      .find('.ace_text-input')
+      .type('/COUNT(DISTINCT name)', { force: true });
+    cy.get('#metrics-edit-popover').find('button').contains('Save').click();
 
     cy.get('button.query').click();
 
diff --git 
a/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts 
b/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts
index 4a21f00..624bac6 100644
--- 
a/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts
+++ 
b/superset-frontend/cypress-base/cypress/integration/explore/advanced.test.ts
@@ -30,14 +30,18 @@ describe('Advanced analytics', () => {
 
     cy.get('.panel-title').contains('Advanced Analytics').click();
 
-    cy.get('[data-test=time_compare]').within(() => {
-      cy.get('.Select__control').click();
-      cy.get('input[type=text]').type('28 days{enter}');
+    cy.get('[data-test=time_compare]').find('.Select__control').click();
+    cy.get('[data-test=time_compare]')
+      .find('input[type=text]')
+      .type('28 days{enter}');
 
-      cy.get('.Select__control').click();
-      cy.get('input[type=text]').type('364 days{enter}');
-      cy.get('.Select__multi-value__label').contains('364 days');
-    });
+    cy.get('[data-test=time_compare]').find('.Select__control').click();
+    cy.get('[data-test=time_compare]')
+      .find('input[type=text]')
+      .type('364 days{enter}');
+    cy.get('[data-test=time_compare]')
+      .find('.Select__multi-value__label')
+      .contains('364 days');
 
     cy.get('button.query').click();
     cy.wait('@postJson');
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 d7a4509..ef4fef5 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
@@ -46,16 +46,10 @@ describe('Datasource control', () => {
     cy.get('.modal-footer button').contains('Save').click();
     cy.get('.modal-footer button').contains('OK').click();
     // select new metric
-    cy.get('.metrics-select:eq(0)').click();
-    cy.get('.metrics-select:eq(0) input[type="text"]')
+    cy.get('[data-test=metrics]')
+      .find('.Select__control input')
       .focus()
-      .type(newMetricName);
-    cy.get('.metrics-select:eq(0) .Select__menu .Select__option')
-      .contains(newMetricName)
-      .click();
-    cy.get('.metrics-select:eq(0) .Select__multi-value__label')
-      .contains(newMetricName)
-      .click();
+      .type(newMetricName, { force: true });
     // delete metric
     cy.get('#datasource_menu').click();
     cy.get('a').contains('Edit Datasource').click();
diff --git 
a/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js 
b/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
index b9793b3..4be0a45 100644
--- 
a/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
+++ 
b/superset-frontend/cypress-base/cypress/integration/explore/filter_box.test.js
@@ -35,8 +35,6 @@ describe('Edit FilterBox Chart', () => {
   it('should work with default date filter', () => {
     verify(VIZ_DEFAULTS);
     // Filter box should default to having a date filter with no filter 
selected
-    cy.get('div.filter_box').within(() => {
-      cy.get('span').contains('No filter');
-    });
+    cy.get('div.filter_box').contains('No filter');
   });
 });
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 76a9086..5c64007 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
@@ -46,9 +46,9 @@ describe('Visualization > Line', () => {
   });
 
   it('should allow negative values in Y bounds', () => {
-    cy.get('#controlSections-tab-display').click();
+    cy.get('#controlSections-tab-display').click().wait(1000);
     cy.get('span').contains('Y Axis Bounds').scrollIntoView();
-    cy.get('input[placeholder="Min"]').type('-0.1', { delay: 100 });
+    cy.get('input[placeholder="Min"]').type('-0.1', { delay: 100 }).wait(1000);
     cy.get('.alert-warning').should('not.exist');
   });
 
diff --git a/superset-frontend/src/components/ListView/ListView.tsx 
b/superset-frontend/src/components/ListView/ListView.tsx
index 0afc44b..2f797e9 100644
--- a/superset-frontend/src/components/ListView/ListView.tsx
+++ b/superset-frontend/src/components/ListView/ListView.tsx
@@ -50,6 +50,9 @@ const ListViewStyles = styled.div`
           background: white;
           position: sticky;
           top: 0;
+          &:first-of-type {
+            padding-left: ${({ theme }) => theme.gridUnit * 4}px;
+          }
         }
       }
     }
@@ -152,6 +155,11 @@ const ListViewStyles = styled.div`
       overflow: hidden;
       white-space: nowrap;
       max-width: 300px;
+      line-height: 1;
+      vertical-align: middle;
+      &:first-of-type {
+        padding-left: ${({ theme }) => theme.gridUnit * 4}px;
+      }
     }
 
     .sort-icon {
diff --git 
a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx 
b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx
index 8f7be4e..e94a7bb 100644
--- a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx
+++ b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx
@@ -271,6 +271,7 @@ export default class AdhocMetricEditPopover extends 
React.Component {
             className="adhoc-metric-edit-tab"
             eventKey={EXPRESSION_TYPES.SQL}
             title="Custom SQL"
+            data-test="adhoc-metric-edit-tab#custom"
           >
             {this.props.datasourceType !== 'druid' ? (
               <FormGroup>
@@ -304,11 +305,16 @@ export default class AdhocMetricEditPopover extends 
React.Component {
             bsStyle={hasUnsavedChanges && stateIsValid ? 'primary' : 'default'}
             bsSize="small"
             className="m-r-5"
+            data-test="AdhocMetricEdit#save"
             onClick={this.onSave}
           >
             Save
           </Button>
-          <Button bsSize="small" onClick={this.props.onClose}>
+          <Button
+            bsSize="small"
+            onClick={this.props.onClose}
+            data-test="AdhocMetricEdit#cancel"
+          >
             Close
           </Button>
           <i
diff --git 
a/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx 
b/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx
index 5b5b926..d4eed6f 100644
--- a/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx
+++ b/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx
@@ -70,6 +70,7 @@ export default class AdhocMetricEditPopoverTitle extends 
React.Component {
         value={adhocMetric.hasCustomLabel ? adhocMetric.label : ''}
         autoFocus
         onChange={onChange}
+        data-test="AdhocMetricEditTitle#input"
       />
     ) : (
       <OverlayTrigger
@@ -81,7 +82,10 @@ export default class AdhocMetricEditPopoverTitle extends 
React.Component {
         onBlur={this.onBlur}
         className="AdhocMetricEditPopoverTitle"
       >
-        <span className="inline-editable">
+        <span
+          className="inline-editable"
+          data-test="AdhocMetricEditTitle#trigger"
+        >
           {adhocMetric.hasCustomLabel ? adhocMetric.label : 'My Metric'}
           &nbsp;
           <i
diff --git 
a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx 
b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
index ff451ff..c3c175b 100644
--- a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
+++ b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
@@ -23,6 +23,7 @@ import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
 import { Alert, Tab, Tabs } from 'react-bootstrap';
 import { t } from '@superset-ui/translation';
+import styled from '@superset-ui/style';
 
 import ControlPanelSection from './ControlPanelSection';
 import ControlRow from './ControlRow';
@@ -40,6 +41,25 @@ const propTypes = {
   isDatasourceMetaLoading: PropTypes.bool.isRequired,
 };
 
+const Styles = styled.div`
+  max-height: 100%;
+  .remove-alert {
+    cursor: 'pointer';
+  }
+  #controlSections {
+    display: flex;
+    flex-direction: column;
+    max-height: 100%;
+  }
+  .nav-tabs {
+    flex: 0 0 1;
+  }
+  .tab-content {
+    overflow: auto;
+    flex: 1 1 100%;
+  }
+`;
+
 class ControlPanelsContainer extends React.Component {
   constructor(props) {
     super(props);
@@ -145,6 +165,7 @@ class ControlPanelsContainer extends React.Component {
       </ControlPanelSection>
     );
   }
+
   render() {
     const querySectionsToRender = [];
     const displaySectionsToRender = [];
@@ -170,32 +191,30 @@ class ControlPanelsContainer extends React.Component {
     });
 
     return (
-      <div className="scrollbar-container">
-        <div className="scrollbar-content">
-          {this.props.alert && (
-            <Alert bsStyle="warning">
-              {this.props.alert}
-              <i
-                role="button"
-                tabIndex={0}
-                className="fa fa-close pull-right"
-                onClick={this.removeAlert}
-                style={{ cursor: 'pointer' }}
-              />
-            </Alert>
-          )}
-          <Tabs id="controlSections">
-            <Tab eventKey="query" title={t('Data')}>
-              {querySectionsToRender.map(this.renderControlPanelSection)}
+      <Styles>
+        {this.props.alert && (
+          <Alert bsStyle="warning">
+            {this.props.alert}
+            <i
+              role="button"
+              tabIndex={0}
+              className="fa fa-close pull-right"
+              onClick={this.removeAlert}
+              style={{ cursor: 'pointer' }}
+            />
+          </Alert>
+        )}
+        <Tabs id="controlSections">
+          <Tab eventKey="query" title={t('Data')}>
+            {querySectionsToRender.map(this.renderControlPanelSection)}
+          </Tab>
+          {displaySectionsToRender.length > 0 && (
+            <Tab eventKey="display" title={t('Customize')}>
+              {displaySectionsToRender.map(this.renderControlPanelSection)}
             </Tab>
-            {displaySectionsToRender.length > 0 && (
-              <Tab eventKey="display" title={t('Customize')}>
-                {displaySectionsToRender.map(this.renderControlPanelSection)}
-              </Tab>
-            )}
-          </Tabs>
-        </div>
-      </div>
+          )}
+        </Tabs>
+      </Styles>
     );
   }
 }
diff --git a/superset-frontend/src/explore/components/ExploreChartPanel.jsx 
b/superset-frontend/src/explore/components/ExploreChartPanel.jsx
index 36975a0..74eaed9 100644
--- a/superset-frontend/src/explore/components/ExploreChartPanel.jsx
+++ b/superset-frontend/src/explore/components/ExploreChartPanel.jsx
@@ -18,8 +18,8 @@
  */
 import React from 'react';
 import PropTypes from 'prop-types';
-import { Panel } from 'react-bootstrap';
 import { ParentSize } from '@vx/responsive';
+import styled from '@superset-ui/style';
 import { chartPropShape } from '../../dashboard/util/propShapes';
 import ChartContainer from '../../chart/ChartContainer';
 import ExploreChartHeader from './ExploreChartHeader';
@@ -49,6 +49,19 @@ const propTypes = {
   triggerRender: PropTypes.bool,
 };
 
+const Styles = styled.div`
+  background-color: ${({ theme }) => theme.colors.grayscale.light5};
+  padding: ${({ theme }) => theme.gridUnit * 4}px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+  align-content: stretch;
+  div:last-of-type {
+    flex-basis: 100%;
+  }
+`;
+
 class ExploreChartPanel extends React.PureComponent {
   renderChart() {
     const { chart } = this.props;
@@ -113,13 +126,12 @@ class ExploreChartPanel extends React.PureComponent {
         chart={this.props.chart}
       />
     );
+
     return (
-      <div className="chart-container">
-        <Panel style={{ height: this.props.height }}>
-          <Panel.Heading>{header}</Panel.Heading>
-          <Panel.Body>{this.renderChart()}</Panel.Body>
-        </Panel>
-      </div>
+      <Styles className="chart-container">
+        <div>{header}</div>
+        <div>{this.renderChart()}</div>
+      </Styles>
     );
   }
 }
diff --git a/superset-frontend/src/explore/components/ExploreViewContainer.jsx 
b/superset-frontend/src/explore/components/ExploreViewContainer.jsx
index 40fb3fd..6104216 100644
--- a/superset-frontend/src/explore/components/ExploreViewContainer.jsx
+++ b/superset-frontend/src/explore/components/ExploreViewContainer.jsx
@@ -21,6 +21,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
+import styled from '@superset-ui/style';
 import { t } from '@superset-ui/translation';
 
 import ExploreChartPanel from './ExploreChartPanel';
@@ -40,20 +41,6 @@ import {
   LOG_ACTIONS_MOUNT_EXPLORER,
   LOG_ACTIONS_CHANGE_EXPLORE_CONTROLS,
 } from '../../logger/LogUtils';
-import Hotkeys from '../../components/Hotkeys';
-
-// Prolly need to move this to a global context
-const keymap = {
-  RUN: 'ctrl + r, ctrl + enter',
-  SAVE: 'ctrl + s',
-};
-
-const getHotKeys = () =>
-  Object.keys(keymap).map(k => ({
-    name: k,
-    descr: keymap[k],
-    key: k,
-  }));
 
 const propTypes = {
   actions: PropTypes.object.isRequired,
@@ -70,6 +57,25 @@ const propTypes = {
   impressionId: PropTypes.string,
 };
 
+const Styles = styled.div`
+  height: ${({ height }) => height};
+  min-height: ${({ height }) => height};
+  overflow: hidden;
+  text-align: left;
+  position: relative;
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  flex-wrap: nowrap;
+  align-items: stretch;
+  .control-pane {
+    display: flex;
+    flex-direction: column;
+    padding: 0 ${({ theme }) => 2 * theme.gridUnit}px;
+    max-height: 100%;
+  }
+`;
+
 class ExploreViewContainer extends React.Component {
   constructor(props) {
     super(props);
@@ -325,12 +331,9 @@ class ExploreViewContainer extends React.Component {
     if (this.props.standalone) {
       return this.renderChartContainer();
     }
+
     return (
-      <div
-        id="explore-container"
-        className="container-fluid"
-        style={{ height: this.state.height, overflow: 'hidden' }}
-      >
+      <Styles id="explore-container" height={this.state.height}>
         {this.state.showModal && (
           <SaveModal
             onHide={this.toggleModal}
@@ -339,45 +342,27 @@ class ExploreViewContainer extends React.Component {
             sliceName={this.props.sliceName}
           />
         )}
-        <div className="row">
-          <div className="col-sm-4">
-            <div
-              style={{
-                display: 'flex',
-                flexDirection: 'row',
-                alignItems: 'center',
-              }}
-            >
-              <QueryAndSaveBtns
-                canAdd={!!(this.props.can_add || this.props.can_overwrite)}
-                onQuery={this.onQuery}
-                onSave={this.toggleModal}
-                onStop={this.onStop}
-                loading={this.props.chart.chartStatus === 'loading'}
-                chartIsStale={this.state.chartIsStale}
-                errorMessage={this.renderErrorMessage()}
-                datasourceType={this.props.datasource_type}
-              />
-              <div className="m-l-5 text-muted">
-                <Hotkeys
-                  header="Keyboard shortcuts"
-                  hotkeys={getHotKeys()}
-                  placement="right"
-                />
-              </div>
-            </div>
-            <br />
-            <ControlPanelsContainer
-              actions={this.props.actions}
-              form_data={this.props.form_data}
-              controls={this.props.controls}
-              datasource_type={this.props.datasource_type}
-              isDatasourceMetaLoading={this.props.isDatasourceMetaLoading}
-            />
-          </div>
-          <div className="col-sm-8">{this.renderChartContainer()}</div>
+        <div className="col-sm-4 control-pane">
+          <QueryAndSaveBtns
+            canAdd={!!(this.props.can_add || this.props.can_overwrite)}
+            onQuery={this.onQuery}
+            onSave={this.toggleModal}
+            onStop={this.onStop}
+            loading={this.props.chart.chartStatus === 'loading'}
+            chartIsStale={this.state.chartIsStale}
+            errorMessage={this.renderErrorMessage()}
+            datasourceType={this.props.datasource_type}
+          />
+          <ControlPanelsContainer
+            actions={this.props.actions}
+            form_data={this.props.form_data}
+            controls={this.props.controls}
+            datasource_type={this.props.datasource_type}
+            isDatasourceMetaLoading={this.props.isDatasourceMetaLoading}
+          />
         </div>
-      </div>
+        <div className="col-sm-8">{this.renderChartContainer()}</div>
+      </Styles>
     );
   }
 }
@@ -433,6 +418,7 @@ function mapDispatchToProps(dispatch) {
 }
 
 export { ExploreViewContainer };
+
 export default connect(
   mapStateToProps,
   mapDispatchToProps,
diff --git a/superset-frontend/src/explore/components/QueryAndSaveBtns.css 
b/superset-frontend/src/explore/components/QueryAndSaveBtns.css
deleted file mode 100644
index 4ea4dce..0000000
--- a/superset-frontend/src/explore/components/QueryAndSaveBtns.css
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * 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.
- */
-.save-btn {
-  width: 100px;
-}
diff --git a/superset-frontend/src/explore/components/QueryAndSaveBtns.jsx 
b/superset-frontend/src/explore/components/QueryAndSaveBtns.jsx
index e2ec4ef..a8bba79 100644
--- a/superset-frontend/src/explore/components/QueryAndSaveBtns.jsx
+++ b/superset-frontend/src/explore/components/QueryAndSaveBtns.jsx
@@ -21,9 +21,10 @@ import PropTypes from 'prop-types';
 import { ButtonGroup, OverlayTrigger, Tooltip } from 'react-bootstrap';
 import classnames from 'classnames';
 import { t } from '@superset-ui/translation';
+import styled from '@superset-ui/style';
 
 import Button from '../../components/Button';
-import './QueryAndSaveBtns.css';
+import Hotkeys from '../../components/Hotkeys';
 
 const propTypes = {
   canAdd: PropTypes.bool.isRequired,
@@ -41,6 +42,30 @@ const defaultProps = {
   disabled: false,
 };
 
+// Prolly need to move this to a global context
+const keymap = {
+  RUN: 'ctrl + r, ctrl + enter',
+  SAVE: 'ctrl + s',
+};
+
+const getHotKeys = () =>
+  Object.keys(keymap).map(k => ({
+    name: k,
+    descr: keymap[k],
+    key: k,
+  }));
+
+const Styles = styled.div`
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding-bottom: ${({ theme }) => 2 * theme.gridUnit}px;
+
+  .save-btn {
+    width: 100px;
+  }
+`;
+
 export default function QueryAndSaveBtns({
   canAdd,
   onQuery,
@@ -79,33 +104,42 @@ export default function QueryAndSaveBtns({
   );
 
   return (
-    <div>
-      <ButtonGroup className="query-and-save">
-        {qryOrStopButton}
-        <Button
-          className={saveClasses}
-          data-target="#save_modal"
-          data-toggle="modal"
-          disabled={saveButtonDisabled}
-          onClick={onSave}
-        >
-          <i className="fa fa-plus-circle" /> Save
-        </Button>
-      </ButtonGroup>
-      {errorMessage && (
-        <span>
-          {' '}
-          <OverlayTrigger
-            placement="right"
-            overlay={
-              <Tooltip id={'query-error-tooltip'}>{errorMessage}</Tooltip>
-            }
+    <Styles>
+      <div>
+        <ButtonGroup className="query-and-save">
+          {qryOrStopButton}
+          <Button
+            className={saveClasses}
+            data-target="#save_modal"
+            data-toggle="modal"
+            disabled={saveButtonDisabled}
+            onClick={onSave}
           >
-            <i className="fa fa-exclamation-circle text-danger fa-lg" />
-          </OverlayTrigger>
-        </span>
-      )}
-    </div>
+            <i className="fa fa-plus-circle" /> Save
+          </Button>
+        </ButtonGroup>
+        {errorMessage && (
+          <span>
+            {' '}
+            <OverlayTrigger
+              placement="right"
+              overlay={
+                <Tooltip id={'query-error-tooltip'}>{errorMessage}</Tooltip>
+              }
+            >
+              <i className="fa fa-exclamation-circle text-danger fa-lg" />
+            </OverlayTrigger>
+          </span>
+        )}
+      </div>
+      <div className="m-l-5 text-muted">
+        <Hotkeys
+          header="Keyboard shortcuts"
+          hotkeys={getHotKeys()}
+          placement="right"
+        />
+      </div>
+    </Styles>
   );
 }
 
diff --git a/superset-frontend/stylesheets/superset.less 
b/superset-frontend/stylesheets/superset.less
index 3b7b452..4eab5d9 100644
--- a/superset-frontend/stylesheets/superset.less
+++ b/superset-frontend/stylesheets/superset.less
@@ -444,6 +444,9 @@ table.table-no-hover tr:hover {
   line-height: 2px;
   border-radius: 50%;
   box-shadow: 2px 2px 4px -1px fade(@darkest, @opacity-light);
+  i {
+    width: 10px;
+  }
 }
 
 iframe {
diff --git a/superset/templates/superset/basic.html 
b/superset/templates/superset/basic.html
index 3fa2591..a315746 100644
--- a/superset/templates/superset/basic.html
+++ b/superset/templates/superset/basic.html
@@ -21,7 +21,7 @@
 {% from 'superset/partials/asset_bundle.html' import css_bundle, js_bundle 
with context %}
 
 {% set favicons = appbuilder.app.config['FAVICONS'] %}
-
+<!DOCTYPE html>
 <html>
   <head>
     <title>
diff --git a/superset/templates/superset/paper-theme.html 
b/superset/templates/superset/paper-theme.html
index 02e15a2..edf5013 100644
--- a/superset/templates/superset/paper-theme.html
+++ b/superset/templates/superset/paper-theme.html
@@ -520,7 +520,7 @@
       </div>
       <div class="row">
         <div class="col-sm-4">
-          <div class="panel panel-default">
+          <div class="panel panel-default f1">
             <div class="panel-heading">
               <h3 class="panel-title">Panel title</h3>
             </div>
diff --git a/superset/templates/superset/reports/slice_data.html 
b/superset/templates/superset/reports/slice_data.html
index 08c8a55..c478cd0 100644
--- a/superset/templates/superset/reports/slice_data.html
+++ b/superset/templates/superset/reports/slice_data.html
@@ -16,6 +16,7 @@
   specific language governing permissions and limitations
   under the License.
 #}
+<!DOCTYPE html>
 <html>
   <head>
   </head>
diff --git a/superset/templates/superset/theme.html 
b/superset/templates/superset/theme.html
index e159e19..f8ebf8f 100644
--- a/superset/templates/superset/theme.html
+++ b/superset/templates/superset/theme.html
@@ -803,7 +803,7 @@
         <div class="row">
           <div class="col-lg-6">
             <h2 id="nav-tabs">Tabs</h2>
-            <div class="panel panel-default">
+            <div class="panel panel-default f2">
               <div class="panel-body">
                 <div class="bs-component">
                   <ul class="nav nav-tabs">
@@ -1184,13 +1184,13 @@
         <div class="row">
           <div class="col-lg-4">
             <div class="bs-component">
-              <div class="panel panel-default">
+              <div class="panel panel-default f3">
                 <div class="panel-body">
                   Basic panel
                 </div>
               </div>
 
-              <div class="panel panel-default">
+              <div class="panel panel-default f4">
                 <div class="panel-body">
                   Panel content
                 </div>
@@ -1200,7 +1200,7 @@
           </div>
           <div class="col-lg-4">
             <div class="bs-component">
-              <div class="panel panel-default">
+              <div class="panel panel-default f5">
                 <div class="panel-heading">
                   <h3 class="panel-title">Panel default</h3>
                 </div>
diff --git a/superset/templates/superset/traceback.html 
b/superset/templates/superset/traceback.html
index 9e690ef..548c431 100644
--- a/superset/templates/superset/traceback.html
+++ b/superset/templates/superset/traceback.html
@@ -15,14 +15,15 @@
   KIND, either express or implied.  See the License for the
   specific language governing permissions and limitations
   under the License.
-#}
+#}}
+<!DOCTYPE html>
 <html>
   <body>
     <h1>Sorry, something went wrong</h1>
     <h3>500 - Internal Server Error</h3>
-    <hr>
+    <hr />
     <h2>Stacktrace</h2>
-    <hr>
+    <hr />
     <code>
       <pre>
         {{ error_msg }}

Reply via email to