Compare commits

...

7 Commits

Author SHA1 Message Date
Maxime Beauchemin
0fcdfcfd8c 0.23.0 2018-03-05 16:59:08 -08:00
Maxime Beauchemin
663fb0f584 0.23.0rc4 2018-02-21 18:16:13 -08:00
Maxime Beauchemin
05367a0c10 Allowing config flag to turn off javascript controls (#4400)
* Allowing config flag to turn off javascript controls

* lint

* one line, avoiding mutation

* Setting JS fields as readOnly

(cherry picked from commit a373db24f0)
2018-02-21 18:15:54 -08:00
Grace Guo
f99c22af0f for 48 columns layout, adjust default size and layout for newly added slices (#4446)
(cherry picked from commit 5768a1fe5e)
2018-02-21 18:15:48 -08:00
Maxime Beauchemin
a1173f4346 [explore] allow URL shortner even if no slice exist (#4457)
recent regression perhaps from the PR that moved to using POST .

(cherry picked from commit 0eecec10cd)
2018-02-21 18:15:41 -08:00
Maxime Beauchemin
91c5ce9080 [bugfix] address issue 4206 (#4452)
closes 4206

(cherry picked from commit 177d7c07e6)
2018-02-21 18:15:34 -08:00
Maxime Beauchemin
16787ee8f8 0.23.0rc3 2018-02-15 21:30:08 -08:00
11 changed files with 76 additions and 35 deletions

View File

@@ -39,16 +39,21 @@ export function getInitialState(bootstrapData) {
dashboard.posDict[position.slice_id] = position;
});
}
dashboard.slices.forEach((slice, index) => {
const lastRowId = Math.max.apply(null,
dashboard.position_json.map(pos => (pos.row + pos.size_y)));
let newSliceCounter = 0;
dashboard.slices.forEach((slice) => {
const sliceId = slice.slice_id;
let pos = dashboard.posDict[sliceId];
if (!pos) {
// append new slices to dashboard bottom, 3 slices per row
pos = {
col: (index * 4 + 1) % 12,
row: Math.floor((index) / 3) * 4,
size_x: 4,
size_y: 4,
col: (newSliceCounter % 3) * 16 + 1,
row: lastRowId + Math.floor(newSliceCounter / 3) * 16,
size_x: 16,
size_y: 16,
};
newSliceCounter++;
}
dashboard.layout.push({

View File

@@ -14,6 +14,7 @@ const propTypes = {
onClick: PropTypes.func,
hovered: PropTypes.bool,
tooltipOnClick: PropTypes.func,
warning: PropTypes.string,
};
const defaultProps = {
@@ -75,6 +76,19 @@ export default class ControlHeader extends React.Component {
{this.props.label}
</span>
{' '}
{(this.props.warning) &&
<span>
<OverlayTrigger
placement="top"
overlay={
<Tooltip id={'error-tooltip'}>{this.props.warning}</Tooltip>
}
>
<i className="fa fa-exclamation-circle text-danger" />
</OverlayTrigger>
{' '}
</span>
}
{(this.props.validationErrors.length > 0) &&
<span>
<OverlayTrigger

View File

@@ -9,27 +9,28 @@ import { exportChart } from '../exploreUtils';
const propTypes = {
canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
slice: PropTypes.object,
chartStatus: PropTypes.string,
latestQueryFormData: PropTypes.object,
queryResponse: PropTypes.object,
};
export default function ExploreActionButtons({
canDownload, slice, chartStatus, latestQueryFormData, queryResponse }) {
canDownload, chartStatus, latestQueryFormData, queryResponse }) {
const exportToCSVClasses = cx('btn btn-default btn-sm', {
'disabled disabledButton': !canDownload,
});
const doExportCSV = exportChart.bind(this, latestQueryFormData, 'csv');
const doExportChart = exportChart.bind(this, latestQueryFormData, 'json');
if (slice) {
return (
<div className="btn-group results" role="group">
<URLShortLinkButton latestQueryFormData={latestQueryFormData} />
return (
<div className="btn-group results" role="group">
{latestQueryFormData &&
<URLShortLinkButton latestQueryFormData={latestQueryFormData} />}
<EmbedCodeButton latestQueryFormData={latestQueryFormData} />
{latestQueryFormData &&
<EmbedCodeButton latestQueryFormData={latestQueryFormData} />}
{latestQueryFormData &&
<a
onClick={doExportChart}
className="btn btn-default btn-sm"
@@ -38,8 +39,8 @@ export default function ExploreActionButtons({
rel="noopener noreferrer"
>
<i className="fa fa-file-code-o" /> .json
</a>
</a>}
{latestQueryFormData &&
<a
onClick={doExportCSV}
className={exportToCSVClasses}
@@ -48,18 +49,13 @@ export default function ExploreActionButtons({
rel="noopener noreferrer"
>
<i className="fa fa-file-text-o" /> .csv
</a>
<DisplayQueryButton
queryResponse={queryResponse}
latestQueryFormData={latestQueryFormData}
chartStatus={chartStatus}
/>
</div>
);
}
return (
<DisplayQueryButton latestQueryFormData={latestQueryFormData} />
</a>}
<DisplayQueryButton
queryResponse={queryResponse}
latestQueryFormData={latestQueryFormData}
chartStatus={chartStatus}
/>
</div>
);
}

View File

@@ -25,6 +25,7 @@ const propTypes = {
offerEditInModal: PropTypes.bool,
language: PropTypes.oneOf([null, 'json', 'html', 'sql', 'markdown', 'javascript']),
aboveEditorSection: PropTypes.node,
readOnly: PropTypes.bool,
};
const defaultProps = {
@@ -34,6 +35,7 @@ const defaultProps = {
minLines: 3,
maxLines: 10,
offerEditInModal: true,
readOnly: false,
};
export default class TextAreaControl extends React.Component {
@@ -57,6 +59,7 @@ export default class TextAreaControl extends React.Component {
editorProps={{ $blockScrolling: true }}
enableLiveAutocompletion
value={this.props.value}
readOnly={this.props.readOnly}
/>
);
}
@@ -67,6 +70,7 @@ export default class TextAreaControl extends React.Component {
placeholder={t('textarea')}
onChange={this.onControlChange.bind(this)}
value={this.props.value}
disabled={this.props.readOnly}
style={{ height: this.props.height }}
/>
</FormGroup>);

View File

@@ -97,6 +97,11 @@ function jsFunctionControl(label, description, extraDescr = null, height = 100,
{extraDescr}
</div>
),
mapStateToProps: state => ({
warning: !state.common.conf.ENABLE_JAVASCRIPT_CONTROLS ?
t('This functionality is disabled in your environment for security reasons.') : null,
readOnly: !state.common.conf.ENABLE_JAVASCRIPT_CONTROLS,
}),
};
}

View File

@@ -1,6 +1,6 @@
{
"name": "superset",
"version": "0.23.0dev",
"version": "0.23.0",
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
"license": "Apache-2.0",
"directories": {

View File

@@ -8,12 +8,7 @@ import ExploreActionButtons from
describe('ExploreActionButtons', () => {
const defaultProps = {
canDownload: 'True',
slice: {
data: {
csv_endpoint: '',
json_endpoint: '',
},
},
latestQueryFormData: {},
queryEndpoint: 'localhost',
};

View File

@@ -370,6 +370,12 @@ TRACKING_URL_TRANSFORMER = lambda x: x # noqa: E731
# Interval between consecutive polls when using Hive Engine
HIVE_POLL_INTERVAL = 5
# Allow for javascript controls components
# this enables programmers to customize certain charts (like the
# geospatial ones) by inputing javascript in controls. This exposes
# an XSS security vulnerability
ENABLE_JAVASCRIPT_CONTROLS = False
try:
if CONFIG_PATH_ENV_VAR in os.environ:
# Explicitly import config module that is not in pythonpath; useful

View File

@@ -279,6 +279,8 @@ class TableModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): # noqa
__('Refresh column metadata'),
'fa-refresh')
def refresh(self, tables):
if not isinstance(tables, list):
tables = [tables]
for t in tables:
t.fetch_metadata()
msg = _(

View File

@@ -19,7 +19,10 @@ from superset.connectors.connector_registry import ConnectorRegistry
from superset.connectors.sqla.models import SqlaTable
from superset.translations.utils import get_language_pack
FRONTEND_CONF_KEYS = ('SUPERSET_WEBSERVER_TIMEOUT',)
FRONTEND_CONF_KEYS = (
'SUPERSET_WEBSERVER_TIMEOUT',
'ENABLE_JAVASCRIPT_CONTROLS',
)
def get_error_msg():

View File

@@ -73,6 +73,14 @@ if perms_instruction_link:
else:
DATASOURCE_ACCESS_ERR = __("You don't have access to this datasource")
FORM_DATA_KEY_BLACKLIST = []
if not config.get('ENABLE_JAVASCRIPT_CONTROLS'):
FORM_DATA_KEY_BLACKLIST = [
'js_tooltip',
'js_onclick_href',
'js_data_mutator',
]
def get_database_access_error_msg(database_name):
return __('This view requires the database %(name)s or '
@@ -948,7 +956,10 @@ class Superset(BaseSupersetView):
if request.args.get('viz_type'):
# Converting old URLs
d = cast_form_data(request.args)
d = cast_form_data(d)
d = {k: v for k, v in d.items() if k not in FORM_DATA_KEY_BLACKLIST}
return d
def get_viz(