mirror of
https://github.com/apache/superset.git
synced 2026-05-01 14:04:21 +00:00
Compare commits
8 Commits
docs/testi
...
0.23.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0c9763975 | ||
|
|
0fcdfcfd8c | ||
|
|
663fb0f584 | ||
|
|
05367a0c10 | ||
|
|
f99c22af0f | ||
|
|
a1173f4346 | ||
|
|
91c5ce9080 | ||
|
|
16787ee8f8 |
@@ -2,6 +2,9 @@ recursive-include superset/data *
|
|||||||
recursive-include superset/migrations *
|
recursive-include superset/migrations *
|
||||||
recursive-include superset/static *
|
recursive-include superset/static *
|
||||||
recursive-exclude superset/static/docs *
|
recursive-exclude superset/static/docs *
|
||||||
|
recursive-exclude superset/static/assets/docs *
|
||||||
|
recursive-exclude superset/static/assets/visualizations *
|
||||||
|
recursive-exclude superset/static/assets/images/viz_thumbnails_large *
|
||||||
recursive-exclude superset/static/spec *
|
recursive-exclude superset/static/spec *
|
||||||
recursive-exclude superset/static/images/viz_thumbnails_large *
|
recursive-exclude superset/static/images/viz_thumbnails_large *
|
||||||
recursive-exclude superset/static/assets/node_modules *
|
recursive-exclude superset/static/assets/node_modules *
|
||||||
|
|||||||
@@ -39,16 +39,21 @@ export function getInitialState(bootstrapData) {
|
|||||||
dashboard.posDict[position.slice_id] = position;
|
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;
|
const sliceId = slice.slice_id;
|
||||||
let pos = dashboard.posDict[sliceId];
|
let pos = dashboard.posDict[sliceId];
|
||||||
if (!pos) {
|
if (!pos) {
|
||||||
|
// append new slices to dashboard bottom, 3 slices per row
|
||||||
pos = {
|
pos = {
|
||||||
col: (index * 4 + 1) % 12,
|
col: (newSliceCounter % 3) * 16 + 1,
|
||||||
row: Math.floor((index) / 3) * 4,
|
row: lastRowId + Math.floor(newSliceCounter / 3) * 16,
|
||||||
size_x: 4,
|
size_x: 16,
|
||||||
size_y: 4,
|
size_y: 16,
|
||||||
};
|
};
|
||||||
|
newSliceCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboard.layout.push({
|
dashboard.layout.push({
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const propTypes = {
|
|||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
hovered: PropTypes.bool,
|
hovered: PropTypes.bool,
|
||||||
tooltipOnClick: PropTypes.func,
|
tooltipOnClick: PropTypes.func,
|
||||||
|
warning: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
@@ -75,6 +76,19 @@ export default class ControlHeader extends React.Component {
|
|||||||
{this.props.label}
|
{this.props.label}
|
||||||
</span>
|
</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) &&
|
{(this.props.validationErrors.length > 0) &&
|
||||||
<span>
|
<span>
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
|
|||||||
@@ -9,27 +9,28 @@ import { exportChart } from '../exploreUtils';
|
|||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
|
canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
|
||||||
slice: PropTypes.object,
|
|
||||||
chartStatus: PropTypes.string,
|
chartStatus: PropTypes.string,
|
||||||
latestQueryFormData: PropTypes.object,
|
latestQueryFormData: PropTypes.object,
|
||||||
queryResponse: PropTypes.object,
|
queryResponse: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ExploreActionButtons({
|
export default function ExploreActionButtons({
|
||||||
canDownload, slice, chartStatus, latestQueryFormData, queryResponse }) {
|
canDownload, chartStatus, latestQueryFormData, queryResponse }) {
|
||||||
const exportToCSVClasses = cx('btn btn-default btn-sm', {
|
const exportToCSVClasses = cx('btn btn-default btn-sm', {
|
||||||
'disabled disabledButton': !canDownload,
|
'disabled disabledButton': !canDownload,
|
||||||
});
|
});
|
||||||
const doExportCSV = exportChart.bind(this, latestQueryFormData, 'csv');
|
const doExportCSV = exportChart.bind(this, latestQueryFormData, 'csv');
|
||||||
const doExportChart = exportChart.bind(this, latestQueryFormData, 'json');
|
const doExportChart = exportChart.bind(this, latestQueryFormData, 'json');
|
||||||
|
|
||||||
if (slice) {
|
return (
|
||||||
return (
|
<div className="btn-group results" role="group">
|
||||||
<div className="btn-group results" role="group">
|
{latestQueryFormData &&
|
||||||
<URLShortLinkButton latestQueryFormData={latestQueryFormData} />
|
<URLShortLinkButton latestQueryFormData={latestQueryFormData} />}
|
||||||
|
|
||||||
<EmbedCodeButton latestQueryFormData={latestQueryFormData} />
|
{latestQueryFormData &&
|
||||||
|
<EmbedCodeButton latestQueryFormData={latestQueryFormData} />}
|
||||||
|
|
||||||
|
{latestQueryFormData &&
|
||||||
<a
|
<a
|
||||||
onClick={doExportChart}
|
onClick={doExportChart}
|
||||||
className="btn btn-default btn-sm"
|
className="btn btn-default btn-sm"
|
||||||
@@ -38,8 +39,8 @@ export default function ExploreActionButtons({
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<i className="fa fa-file-code-o" /> .json
|
<i className="fa fa-file-code-o" /> .json
|
||||||
</a>
|
</a>}
|
||||||
|
{latestQueryFormData &&
|
||||||
<a
|
<a
|
||||||
onClick={doExportCSV}
|
onClick={doExportCSV}
|
||||||
className={exportToCSVClasses}
|
className={exportToCSVClasses}
|
||||||
@@ -48,18 +49,13 @@ export default function ExploreActionButtons({
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<i className="fa fa-file-text-o" /> .csv
|
<i className="fa fa-file-text-o" /> .csv
|
||||||
</a>
|
</a>}
|
||||||
|
<DisplayQueryButton
|
||||||
<DisplayQueryButton
|
queryResponse={queryResponse}
|
||||||
queryResponse={queryResponse}
|
latestQueryFormData={latestQueryFormData}
|
||||||
latestQueryFormData={latestQueryFormData}
|
chartStatus={chartStatus}
|
||||||
chartStatus={chartStatus}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<DisplayQueryButton latestQueryFormData={latestQueryFormData} />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const propTypes = {
|
|||||||
offerEditInModal: PropTypes.bool,
|
offerEditInModal: PropTypes.bool,
|
||||||
language: PropTypes.oneOf([null, 'json', 'html', 'sql', 'markdown', 'javascript']),
|
language: PropTypes.oneOf([null, 'json', 'html', 'sql', 'markdown', 'javascript']),
|
||||||
aboveEditorSection: PropTypes.node,
|
aboveEditorSection: PropTypes.node,
|
||||||
|
readOnly: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
@@ -34,6 +35,7 @@ const defaultProps = {
|
|||||||
minLines: 3,
|
minLines: 3,
|
||||||
maxLines: 10,
|
maxLines: 10,
|
||||||
offerEditInModal: true,
|
offerEditInModal: true,
|
||||||
|
readOnly: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class TextAreaControl extends React.Component {
|
export default class TextAreaControl extends React.Component {
|
||||||
@@ -57,6 +59,7 @@ export default class TextAreaControl extends React.Component {
|
|||||||
editorProps={{ $blockScrolling: true }}
|
editorProps={{ $blockScrolling: true }}
|
||||||
enableLiveAutocompletion
|
enableLiveAutocompletion
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
|
readOnly={this.props.readOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -67,6 +70,7 @@ export default class TextAreaControl extends React.Component {
|
|||||||
placeholder={t('textarea')}
|
placeholder={t('textarea')}
|
||||||
onChange={this.onControlChange.bind(this)}
|
onChange={this.onControlChange.bind(this)}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
|
disabled={this.props.readOnly}
|
||||||
style={{ height: this.props.height }}
|
style={{ height: this.props.height }}
|
||||||
/>
|
/>
|
||||||
</FormGroup>);
|
</FormGroup>);
|
||||||
|
|||||||
@@ -97,6 +97,11 @@ function jsFunctionControl(label, description, extraDescr = null, height = 100,
|
|||||||
{extraDescr}
|
{extraDescr}
|
||||||
</div>
|
</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",
|
"name": "superset",
|
||||||
"version": "0.23.0dev",
|
"version": "0.23.1",
|
||||||
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|||||||
@@ -8,12 +8,7 @@ import ExploreActionButtons from
|
|||||||
describe('ExploreActionButtons', () => {
|
describe('ExploreActionButtons', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
canDownload: 'True',
|
canDownload: 'True',
|
||||||
slice: {
|
latestQueryFormData: {},
|
||||||
data: {
|
|
||||||
csv_endpoint: '',
|
|
||||||
json_endpoint: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
queryEndpoint: 'localhost',
|
queryEndpoint: 'localhost',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -370,6 +370,12 @@ TRACKING_URL_TRANSFORMER = lambda x: x # noqa: E731
|
|||||||
# Interval between consecutive polls when using Hive Engine
|
# Interval between consecutive polls when using Hive Engine
|
||||||
HIVE_POLL_INTERVAL = 5
|
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:
|
try:
|
||||||
if CONFIG_PATH_ENV_VAR in os.environ:
|
if CONFIG_PATH_ENV_VAR in os.environ:
|
||||||
# Explicitly import config module that is not in pythonpath; useful
|
# Explicitly import config module that is not in pythonpath; useful
|
||||||
|
|||||||
@@ -279,6 +279,8 @@ class TableModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): # noqa
|
|||||||
__('Refresh column metadata'),
|
__('Refresh column metadata'),
|
||||||
'fa-refresh')
|
'fa-refresh')
|
||||||
def refresh(self, tables):
|
def refresh(self, tables):
|
||||||
|
if not isinstance(tables, list):
|
||||||
|
tables = [tables]
|
||||||
for t in tables:
|
for t in tables:
|
||||||
t.fetch_metadata()
|
t.fetch_metadata()
|
||||||
msg = _(
|
msg = _(
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ from superset.connectors.connector_registry import ConnectorRegistry
|
|||||||
from superset.connectors.sqla.models import SqlaTable
|
from superset.connectors.sqla.models import SqlaTable
|
||||||
from superset.translations.utils import get_language_pack
|
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():
|
def get_error_msg():
|
||||||
|
|||||||
@@ -73,6 +73,14 @@ if perms_instruction_link:
|
|||||||
else:
|
else:
|
||||||
DATASOURCE_ACCESS_ERR = __("You don't have access to this datasource")
|
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):
|
def get_database_access_error_msg(database_name):
|
||||||
return __('This view requires the database %(name)s or '
|
return __('This view requires the database %(name)s or '
|
||||||
@@ -948,7 +956,10 @@ class Superset(BaseSupersetView):
|
|||||||
|
|
||||||
if request.args.get('viz_type'):
|
if request.args.get('viz_type'):
|
||||||
# Converting old URLs
|
# 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
|
return d
|
||||||
|
|
||||||
def get_viz(
|
def get_viz(
|
||||||
|
|||||||
Reference in New Issue
Block a user