mirror of
https://github.com/apache/superset.git
synced 2026-04-29 13:04:22 +00:00
Compare commits
10 Commits
fix-app-ro
...
0.23.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf95d12bcd | ||
|
|
c2acd15917 | ||
|
|
c0c9763975 | ||
|
|
0fcdfcfd8c | ||
|
|
663fb0f584 | ||
|
|
05367a0c10 | ||
|
|
f99c22af0f | ||
|
|
a1173f4346 | ||
|
|
91c5ce9080 | ||
|
|
16787ee8f8 |
@@ -2,6 +2,8 @@ recursive-include superset/data *
|
||||
recursive-include superset/migrations *
|
||||
recursive-include superset/static *
|
||||
recursive-exclude superset/static/docs *
|
||||
recursive-exclude superset/static/assets/docs *
|
||||
recursive-exclude superset/static/assets/images/viz_thumbnails_large *
|
||||
recursive-exclude superset/static/spec *
|
||||
recursive-exclude superset/static/images/viz_thumbnails_large *
|
||||
recursive-exclude superset/static/assets/node_modules *
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>);
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superset",
|
||||
"version": "0.23.0dev",
|
||||
"version": "0.23.2",
|
||||
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
||||
"license": "Apache-2.0",
|
||||
"directories": {
|
||||
|
||||
@@ -8,12 +8,7 @@ import ExploreActionButtons from
|
||||
describe('ExploreActionButtons', () => {
|
||||
const defaultProps = {
|
||||
canDownload: 'True',
|
||||
slice: {
|
||||
data: {
|
||||
csv_endpoint: '',
|
||||
json_endpoint: '',
|
||||
},
|
||||
},
|
||||
latestQueryFormData: {},
|
||||
queryEndpoint: 'localhost',
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = _(
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user