Merge pull request #6805 from lyft/xtinec--fix-sticky-tooltip
Fix sticky tooltips on nvd3 vizzes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index dcc79f7..9f0fd5c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -410,6 +410,10 @@
}
```
+`superset/config.py` contains `DEFAULT_FEATURE_FLAGS` which will be overwritten by
+those specified under FEATURE_FLAGS in `superset_config.py`. For example, `DEFAULT_FEATURE_FLAGS = { 'FOO': True, 'BAR': False }` in `superset/config.py` and `FEATURE_FLAGS = { 'BAR': True, 'BAZ': True }` in `superset_config.py` will result
+in combined feature flags of `{ 'FOO': True, 'BAR': True, 'BAZ': True }`.
+
## Linting
Lint the project with:
diff --git a/install/helm/superset/templates/ingress.yaml b/install/helm/superset/templates/ingress.yaml
index 884c38b..b0888e4 100644
--- a/install/helm/superset/templates/ingress.yaml
+++ b/install/helm/superset/templates/ingress.yaml
@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-{{- if .Values.ingress.enabled -}}
+{{ if .Values.ingress.enabled -}}
{{- $fullName := include "superset.fullname" . -}}
{{- $ingressPath := .Values.ingress.path -}}
apiVersion: extensions/v1beta1
diff --git a/superset/__init__.py b/superset/__init__.py
index 791e20b..500bcaa 100644
--- a/superset/__init__.py
+++ b/superset/__init__.py
@@ -208,6 +208,18 @@
results_backend = app.config.get('RESULTS_BACKEND')
+# Merge user defined feature flags with default feature flags
+feature_flags = app.config.get('DEFAULT_FEATURE_FLAGS')
+feature_flags.update(app.config.get('FEATURE_FLAGS') or {})
+
+
+def is_feature_enabled(feature):
+ """
+ Utility function for checking whether a feature is turned on
+ """
+ return feature_flags.get(feature)
+
+
# Registering sources
module_datasource_map = app.config.get('DEFAULT_MODULE_DS_MAP')
module_datasource_map.update(app.config.get('ADDITIONAL_MODULE_DS_MAP'))
diff --git a/superset/assets/src/components/RefreshLabel.jsx b/superset/assets/src/components/RefreshLabel.jsx
index cd5a6e0..855dc66 100644
--- a/superset/assets/src/components/RefreshLabel.jsx
+++ b/superset/assets/src/components/RefreshLabel.jsx
@@ -18,49 +18,26 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
-import { Label } from 'react-bootstrap';
import TooltipWrapper from './TooltipWrapper';
+import './RefreshLabel.less';
+
const propTypes = {
onClick: PropTypes.func,
- className: PropTypes.string,
tooltipContent: PropTypes.string.isRequired,
};
class RefreshLabel extends React.PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- hovered: false,
- };
- }
-
- mouseOver() {
- this.setState({ hovered: true });
- }
-
- mouseOut() {
- this.setState({ hovered: false });
- }
-
render() {
- const labelStyle = this.state.hovered ? 'primary' : 'default';
- const tooltip = 'Click to ' + this.props.tooltipContent;
return (
<TooltipWrapper
- tooltip={tooltip}
+ tooltip={this.props.tooltipContent}
label="cache-desc"
>
- <Label
- className={this.props.className}
- bsStyle={labelStyle}
- style={{ fontSize: '13px', marginRight: '5px', cursor: 'pointer' }}
+ <i
+ className="RefreshLabel fa fa-refresh pointer"
onClick={this.props.onClick}
- onMouseOver={this.mouseOver.bind(this)}
- onMouseOut={this.mouseOut.bind(this)}
- >
- <i className="fa fa-refresh" />
- </Label>
+ />
</TooltipWrapper>);
}
}
diff --git a/superset/assets/src/components/RefreshLabel.less b/superset/assets/src/components/RefreshLabel.less
new file mode 100644
index 0000000..3bf895b
--- /dev/null
+++ b/superset/assets/src/components/RefreshLabel.less
@@ -0,0 +1,27 @@
+/**
+ * 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 "../../stylesheets/less/cosmo/variables.less";
+
+.RefreshLabel:hover {
+ color: @brand-primary;
+}
+
+.RefreshLabel {
+ color: @gray-light;
+}
diff --git a/superset/assets/src/components/TableSelector.css b/superset/assets/src/components/TableSelector.css
new file mode 100644
index 0000000..b4636de
--- /dev/null
+++ b/superset/assets/src/components/TableSelector.css
@@ -0,0 +1,24 @@
+/**
+ * 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.
+ */
+.TableSelector .fa-refresh {
+ padding-top: 7px
+}
+.TableSelector .refresh-col {
+ padding-left: 0px;
+}
diff --git a/superset/assets/src/components/TableSelector.jsx b/superset/assets/src/components/TableSelector.jsx
index 6a0c401..9031d1a 100644
--- a/superset/assets/src/components/TableSelector.jsx
+++ b/superset/assets/src/components/TableSelector.jsx
@@ -20,12 +20,13 @@
import PropTypes from 'prop-types';
import Select from 'react-virtualized-select';
import createFilterOptions from 'react-select-fast-filter-options';
-import { ControlLabel, Col, Label } from 'react-bootstrap';
+import { ControlLabel, Col, Label, Row } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection';
import AsyncSelect from './AsyncSelect';
import RefreshLabel from './RefreshLabel';
+import './TableSelector.css';
const propTypes = {
dbId: PropTypes.number.isRequired,
@@ -196,8 +197,16 @@
{db.database_name}
</span>);
}
- renderDatabaseSelect() {
+ renderSelectRow(select, refreshBtn) {
return (
+ <Row>
+ <Col md={11}>{select}</Col>
+ <Col md={1} className="refresh-col">{refreshBtn}</Col>
+ </Row>
+ );
+ }
+ renderDatabaseSelect() {
+ return this.renderSelectRow(
<AsyncSelect
dataEndpoint={
'/databaseasync/api/' +
@@ -225,30 +234,26 @@
renderSchema() {
return (
<div className="m-t-5">
- <div className="row">
- <div className="col-md-11 col-xs-11 p-r-2">
- <Select
- name="select-schema"
- placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
- options={this.state.schemaOptions}
- value={this.props.schema}
- valueRenderer={o => (
- <div>
- <span className="text-muted">{t('Schema:')}</span> {o.label}
- </div>
- )}
- isLoading={this.state.schemaLoading}
- autosize={false}
- onChange={this.changeSchema}
- />
- </div>
- <div className="col-md-1 col-xs-1 p-l-0 p-t-8">
- <RefreshLabel
- onClick={() => this.onDatabaseChange({ id: this.props.dbId }, true)}
- tooltipContent={t('force refresh schema list')}
- />
- </div>
- </div>
+ {this.renderSelectRow(
+ <Select
+ name="select-schema"
+ placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
+ options={this.state.schemaOptions}
+ value={this.props.schema}
+ valueRenderer={o => (
+ <div>
+ <span className="text-muted">{t('Schema:')}</span> {o.label}
+ </div>
+ )}
+ isLoading={this.state.schemaLoading}
+ autosize={false}
+ onChange={this.changeSchema}
+ />,
+ <RefreshLabel
+ onClick={() => this.onDatabaseChange({ id: this.props.dbId }, true)}
+ tooltipContent={t('Force refresh schema list')}
+ />,
+ )}
</div>
);
}
@@ -262,43 +267,37 @@
tableSelectDisabled = true;
}
const options = this.addOptionIfMissing(this.state.tableOptions, this.state.tableName);
+ const select = this.props.schema ? (
+ <Select
+ name="select-table"
+ ref="selectTable"
+ isLoading={this.state.tableLoading}
+ placeholder={t('Select table or type table name')}
+ autosize={false}
+ onChange={this.changeTable}
+ filterOptions={this.state.filterOptions}
+ options={options}
+ value={this.state.tableName}
+ />) : (
+ <Select
+ async
+ name="async-select-table"
+ ref="selectTable"
+ placeholder={tableSelectPlaceholder}
+ disabled={tableSelectDisabled}
+ autosize={false}
+ onChange={this.changeTable}
+ value={this.state.tableName}
+ loadOptions={this.getTableNamesBySubStr}
+ />);
return (
<div className="m-t-5">
- <div className="row">
- <div className="col-md-11 col-xs-11 p-r-2">
- {this.props.schema ? (
- <Select
- name="select-table"
- ref="selectTable"
- isLoading={this.state.tableLoading}
- placeholder={t('Select table or type table name')}
- autosize={false}
- onChange={this.changeTable}
- filterOptions={this.state.filterOptions}
- options={options}
- value={this.state.tableName}
- />
- ) : (
- <Select
- async
- name="async-select-table"
- ref="selectTable"
- placeholder={tableSelectPlaceholder}
- disabled={tableSelectDisabled}
- autosize={false}
- onChange={this.changeTable}
- value={this.state.tableName}
- loadOptions={this.getTableNamesBySubStr}
- />
- )}
- </div>
- <div className="col-md-1 col-xs-1 p-l-0 p-t-8">
- <RefreshLabel
- onClick={() => this.changeSchema({ value: this.props.schema }, true)}
- tooltipContent={t('force refresh table list')}
- />
- </div>
- </div>
+ {this.renderSelectRow(
+ select,
+ <RefreshLabel
+ onClick={() => this.changeSchema({ value: this.props.schema }, true)}
+ tooltipContent={t('Force refresh table list')}
+ />)}
</div>);
}
renderSeeTableLabel() {
@@ -318,21 +317,22 @@
</div>);
}
render() {
- if (this.props.horizontal) {
- return (
- <div>
- <Col md={4}>{this.renderDatabaseSelect()}</Col>
- <Col md={4}>{this.renderSchema()}</Col>
- <Col md={4}>{this.renderTable()}</Col>
- </div>);
- }
return (
- <div>
- <div>{this.renderDatabaseSelect()}</div>
- <div className="m-t-5">{this.renderSchema()}</div>
- {this.props.sqlLabMode && this.renderSeeTableLabel()}
- <div className="m-t-5">{this.renderTable()}</div>
- </div>);
+ <div className="TableSelector">
+ {this.props.horizontal ?
+ <div>
+ <Col md={4}>{this.renderDatabaseSelect()}</Col>
+ <Col md={4}>{this.renderSchema()}</Col>
+ <Col md={4}>{this.renderTable()}</Col>
+ </div> :
+ <div>
+ <div>{this.renderDatabaseSelect()}</div>
+ <div className="m-t-5">{this.renderSchema()}</div>
+ {this.props.sqlLabMode && this.renderSeeTableLabel()}
+ <div className="m-t-5">{this.renderTable()}</div>
+ </div>}
+ </div>
+ );
}
}
TableSelector.propTypes = propTypes;
diff --git a/superset/config.py b/superset/config.py
index a598d76..93720c3 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -187,9 +187,12 @@
# ---------------------------------------------------
# Feature flags
# ---------------------------------------------------
-# Feature flags that are on by default go here. Their
-# values can be overridden by those in super_config.py
-FEATURE_FLAGS = {}
+# Feature flags that are set by default go here. Their values can be
+# overwritten by those specified under FEATURE_FLAGS in super_config.py
+# For example, DEFAULT_FEATURE_FLAGS = { 'FOO': True, 'BAR': False } here
+# and FEATURE_FLAGS = { 'BAR': True, 'BAZ': True } in superset_config.py
+# will result in combined feature flags of { 'FOO': True, 'BAR': True, 'BAZ': True }
+DEFAULT_FEATURE_FLAGS = {}
# ---------------------------------------------------
# Image and file configuration
diff --git a/superset/migrations/versions/a33a03f16c4a_add_extra_column_to_savedquery.py b/superset/migrations/versions/a33a03f16c4a_add_extra_column_to_savedquery.py
index 95518e5..07e0b05 100644
--- a/superset/migrations/versions/a33a03f16c4a_add_extra_column_to_savedquery.py
+++ b/superset/migrations/versions/a33a03f16c4a_add_extra_column_to_savedquery.py
@@ -1,3 +1,19 @@
+# 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.
"""Add extra column to SavedQuery
Licensed to the Apache Software Foundation (ASF) under one or more
diff --git a/superset/views/base.py b/superset/views/base.py
index 6b71ff0..f0f5fbf 100644
--- a/superset/views/base.py
+++ b/superset/views/base.py
@@ -31,7 +31,7 @@
import simplejson as json
import yaml
-from superset import conf, db, security_manager
+from superset import conf, db, feature_flags, security_manager
from superset.exceptions import SupersetException, SupersetSecurityException
from superset.translations.utils import get_language_pack
from superset.utils import core as utils
@@ -157,7 +157,7 @@
'conf': {k: conf.get(k) for k in FRONTEND_CONF_KEYS},
'locale': locale,
'language_pack': get_language_pack(locale),
- 'feature_flags': conf.get('FEATURE_FLAGS'),
+ 'feature_flags': feature_flags,
}
diff --git a/tests/base_tests.py b/tests/base_tests.py
index f3029c0..1f3b4c5 100644
--- a/tests/base_tests.py
+++ b/tests/base_tests.py
@@ -19,10 +19,10 @@
import unittest
from flask_appbuilder.security.sqla import models as ab_models
-from mock import Mock
+from mock import Mock, patch
import pandas as pd
-from superset import app, db, security_manager
+from superset import app, db, is_feature_enabled, security_manager
from superset.connectors.druid.models import DruidCluster, DruidDatasource
from superset.connectors.sqla.models import SqlaTable
from superset.models import core as models
@@ -185,3 +185,11 @@
if raise_on_error and 'error' in resp:
raise Exception('run_sql failed')
return resp
+
+ @patch.dict('superset.feature_flags', {'FOO': True}, clear=True)
+ def test_existing_feature_flags(self):
+ self.assertTrue(is_feature_enabled('FOO'))
+
+ @patch.dict('superset.feature_flags', {}, clear=True)
+ def test_nonexistent_feature_flags(self):
+ self.assertFalse(is_feature_enabled('FOO'))