mirror of
https://github.com/apache/superset.git
synced 2026-07-19 13:15:49 +00:00
Compare commits
9 Commits
upgrade-sq
...
0.21.0-pro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c2be6c2fd | ||
|
|
1a0d5420b0 | ||
|
|
393d54bd32 | ||
|
|
7aa939ce13 | ||
|
|
39d9632843 | ||
|
|
53b908322f | ||
|
|
b90a910936 | ||
|
|
459ca55d6d | ||
|
|
8e307a3e4d |
@@ -1,5 +1,5 @@
|
|||||||
Superset
|
Superset
|
||||||
=========
|
==========
|
||||||
|
|
||||||
[](https://travis-ci.org/apache/incubator-superset)
|
[](https://travis-ci.org/apache/incubator-superset)
|
||||||
[](https://badge.fury.io/py/superset)
|
[](https://badge.fury.io/py/superset)
|
||||||
|
|||||||
1
setup.py
1
setup.py
@@ -58,6 +58,7 @@ setup(
|
|||||||
'flask-wtf==0.14.2',
|
'flask-wtf==0.14.2',
|
||||||
'flower==0.9.1',
|
'flower==0.9.1',
|
||||||
'future>=0.16.0, <0.17',
|
'future>=0.16.0, <0.17',
|
||||||
|
'python-geohash==0.8.5',
|
||||||
'humanize==0.5.1',
|
'humanize==0.5.1',
|
||||||
'gunicorn==19.7.1',
|
'gunicorn==19.7.1',
|
||||||
'idna==2.5',
|
'idna==2.5',
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function PopoverSection({ title, isSelected, children, onSelect,
|
|||||||
|
|
||||||
<i className={isSelected ? 'fa fa-check text-primary' : ''} />
|
<i className={isSelected ? 'fa fa-check text-primary' : ''} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="m-t-5 m-l-5">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export default class DateFilterControl extends React.Component {
|
|||||||
renderPopover() {
|
renderPopover() {
|
||||||
return (
|
return (
|
||||||
<Popover id="filter-popover">
|
<Popover id="filter-popover">
|
||||||
<div style={{ width: '240px' }}>
|
<div style={{ width: '250px' }}>
|
||||||
<PopoverSection
|
<PopoverSection
|
||||||
title="Fixed"
|
title="Fixed"
|
||||||
isSelected={this.state.type === 'fix'}
|
isSelected={this.state.type === 'fix'}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const propTypes = {
|
|||||||
valueRenderer: PropTypes.func,
|
valueRenderer: PropTypes.func,
|
||||||
valueKey: PropTypes.string,
|
valueKey: PropTypes.string,
|
||||||
options: PropTypes.array,
|
options: PropTypes.array,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
@@ -105,10 +106,11 @@ export default class SelectControl extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
// Tab, comma or Enter will trigger a new option created for FreeFormSelect
|
// Tab, comma or Enter will trigger a new option created for FreeFormSelect
|
||||||
|
const placeholder = this.props.placeholder || t('Select %s', this.state.options.length);
|
||||||
const selectProps = {
|
const selectProps = {
|
||||||
multi: this.props.multi,
|
multi: this.props.multi,
|
||||||
name: `select-${this.props.name}`,
|
name: `select-${this.props.name}`,
|
||||||
placeholder: t('Select %s', this.state.options.length),
|
placeholder,
|
||||||
options: this.state.options,
|
options: this.state.options,
|
||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
labelKey: 'label',
|
labelKey: 'label',
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
Row, Col, Button, FormControl, Label, OverlayTrigger, Popover,
|
||||||
|
} from 'react-bootstrap';
|
||||||
|
import 'react-datetime/css/react-datetime.css';
|
||||||
|
|
||||||
|
import ControlHeader from '../ControlHeader';
|
||||||
|
import SelectControl from './SelectControl';
|
||||||
|
import PopoverSection from '../../../components/PopoverSection';
|
||||||
|
import Checkbox from '../../../components/Checkbox';
|
||||||
|
import { t } from '../../../locales';
|
||||||
|
|
||||||
|
const spatialTypes = {
|
||||||
|
latlong: 'latlong',
|
||||||
|
delimited: 'delimited',
|
||||||
|
geohash: 'geohash',
|
||||||
|
};
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
value: PropTypes.object,
|
||||||
|
animation: PropTypes.bool,
|
||||||
|
choices: PropTypes.array,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
onChange: () => {},
|
||||||
|
animation: true,
|
||||||
|
choices: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SpatialControl extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const v = props.value || {};
|
||||||
|
let defaultCol;
|
||||||
|
if (props.choices.length > 0) {
|
||||||
|
defaultCol = props.choices[0][0];
|
||||||
|
}
|
||||||
|
this.state = {
|
||||||
|
type: v.type || spatialTypes.latlong,
|
||||||
|
delimiter: v.delimiter || ',',
|
||||||
|
latCol: v.latCol || defaultCol,
|
||||||
|
lonCol: v.lonCol || defaultCol,
|
||||||
|
lonlatCol: v.lonlatCol || defaultCol,
|
||||||
|
reverseCheckbox: v.reverseCheckbox || false,
|
||||||
|
geohashCol: v.geohashCol || defaultCol,
|
||||||
|
value: null,
|
||||||
|
errors: [],
|
||||||
|
};
|
||||||
|
this.onDelimiterChange = this.onDelimiterChange.bind(this);
|
||||||
|
this.toggleCheckbox = this.toggleCheckbox.bind(this);
|
||||||
|
this.onChange = this.onChange.bind(this);
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
this.onChange();
|
||||||
|
}
|
||||||
|
onChange() {
|
||||||
|
const type = this.state.type;
|
||||||
|
const value = { type };
|
||||||
|
const errors = [];
|
||||||
|
const errMsg = t('Invalid lat/long configuration.');
|
||||||
|
if (type === spatialTypes.latlong) {
|
||||||
|
value.latCol = this.state.latCol;
|
||||||
|
value.lonCol = this.state.lonCol;
|
||||||
|
if (!value.lonCol || !value.latCol) {
|
||||||
|
errors.push(errMsg);
|
||||||
|
}
|
||||||
|
} else if (type === spatialTypes.delimited) {
|
||||||
|
value.lonlatCol = this.state.lonlatCol;
|
||||||
|
value.delimiter = this.state.delimiter;
|
||||||
|
value.reverseCheckbox = this.state.reverseCheckbox;
|
||||||
|
if (!value.lonlatCol || !value.delimiter) {
|
||||||
|
errors.push(errMsg);
|
||||||
|
}
|
||||||
|
} else if (type === spatialTypes.geohash) {
|
||||||
|
value.geohashCol = this.state.geohashCol;
|
||||||
|
if (!value.geohashCol) {
|
||||||
|
errors.push(errMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState({ value, errors });
|
||||||
|
this.props.onChange(value, errors);
|
||||||
|
}
|
||||||
|
onDelimiterChange(event) {
|
||||||
|
this.setState({ delimiter: event.target.value }, this.onChange);
|
||||||
|
}
|
||||||
|
setType(type) {
|
||||||
|
this.setState({ type }, this.onChange);
|
||||||
|
}
|
||||||
|
close() {
|
||||||
|
this.refs.trigger.hide();
|
||||||
|
}
|
||||||
|
toggleCheckbox() {
|
||||||
|
this.setState({ reverseCheckbox: !this.state.reverseCheckbox }, this.onChange);
|
||||||
|
}
|
||||||
|
renderLabelContent() {
|
||||||
|
if (this.state.errors.length > 0) {
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
if (this.state.type === spatialTypes.latlong) {
|
||||||
|
return `${this.state.lonCol} | ${this.state.latCol}`;
|
||||||
|
} else if (this.state.type === spatialTypes.delimited) {
|
||||||
|
return `${this.state.lonlatCol}`;
|
||||||
|
} else if (this.state.type === spatialTypes.geohash) {
|
||||||
|
return `${this.state.geohashCol}`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
renderSelect(name, type) {
|
||||||
|
return (
|
||||||
|
<SelectControl
|
||||||
|
name={name}
|
||||||
|
choices={this.props.choices}
|
||||||
|
value={this.state[name]}
|
||||||
|
clearable={false}
|
||||||
|
onFocus={() => {
|
||||||
|
this.setType(type);
|
||||||
|
}}
|
||||||
|
onChange={(value) => {
|
||||||
|
this.setState({ [name]: value }, this.onChange);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
renderPopover() {
|
||||||
|
return (
|
||||||
|
<Popover id="filter-popover">
|
||||||
|
<div style={{ width: '300px' }}>
|
||||||
|
<PopoverSection
|
||||||
|
title="Longitude & Latitude columns"
|
||||||
|
isSelected={this.state.type === spatialTypes.latlong}
|
||||||
|
onSelect={this.setType.bind(this, spatialTypes.latlong)}
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<Col md={6}>
|
||||||
|
Longitude
|
||||||
|
{this.renderSelect('lonCol', spatialTypes.latlong)}
|
||||||
|
</Col>
|
||||||
|
<Col md={6}>
|
||||||
|
Latitude
|
||||||
|
{this.renderSelect('latCol', spatialTypes.latlong)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</PopoverSection>
|
||||||
|
<PopoverSection
|
||||||
|
title="Delimited long & lat single column"
|
||||||
|
isSelected={this.state.type === spatialTypes.delimited}
|
||||||
|
onSelect={this.setType.bind(this, spatialTypes.delimited)}
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<Col md={6}>
|
||||||
|
Column
|
||||||
|
{this.renderSelect('lonlatCol', spatialTypes.delimited)}
|
||||||
|
</Col>
|
||||||
|
<Col md={6}>
|
||||||
|
Delimiter
|
||||||
|
<FormControl
|
||||||
|
onFocus={this.setType.bind(this, spatialTypes.delimited)}
|
||||||
|
value={this.state.delimiter}
|
||||||
|
onChange={this.onDelimiterChange}
|
||||||
|
placeholder="delimiter"
|
||||||
|
bsSize="small"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<div>
|
||||||
|
{t('Reverse lat/long ')}
|
||||||
|
<Checkbox checked={this.state.reverseCheckbox} onChange={this.toggleCheckbox} />
|
||||||
|
</div>
|
||||||
|
</PopoverSection>
|
||||||
|
<PopoverSection
|
||||||
|
title="Geohash"
|
||||||
|
isSelected={this.state.type === spatialTypes.geohash}
|
||||||
|
onSelect={this.setType.bind(this, spatialTypes.geohash)}
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<Col md={6}>
|
||||||
|
Column
|
||||||
|
{this.renderSelect('geohashCol', spatialTypes.geohash)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</PopoverSection>
|
||||||
|
<div className="clearfix">
|
||||||
|
<Button
|
||||||
|
bsSize="small"
|
||||||
|
className="float-left ok"
|
||||||
|
bsStyle="primary"
|
||||||
|
onClick={this.close.bind(this)}
|
||||||
|
>
|
||||||
|
Ok
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ControlHeader {...this.props} />
|
||||||
|
<OverlayTrigger
|
||||||
|
animation={this.props.animation}
|
||||||
|
container={document.body}
|
||||||
|
trigger="click"
|
||||||
|
rootClose
|
||||||
|
ref="trigger"
|
||||||
|
placement="right"
|
||||||
|
overlay={this.renderPopover()}
|
||||||
|
>
|
||||||
|
<Label style={{ cursor: 'pointer' }}>
|
||||||
|
{this.renderLabelContent()}
|
||||||
|
</Label>
|
||||||
|
</OverlayTrigger>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpatialControl.propTypes = propTypes;
|
||||||
|
SpatialControl.defaultProps = defaultProps;
|
||||||
@@ -10,6 +10,7 @@ import FixedOrMetricControl from './FixedOrMetricControl';
|
|||||||
import HiddenControl from './HiddenControl';
|
import HiddenControl from './HiddenControl';
|
||||||
import SelectAsyncControl from './SelectAsyncControl';
|
import SelectAsyncControl from './SelectAsyncControl';
|
||||||
import SelectControl from './SelectControl';
|
import SelectControl from './SelectControl';
|
||||||
|
import SpatialControl from './SpatialControl';
|
||||||
import TextAreaControl from './TextAreaControl';
|
import TextAreaControl from './TextAreaControl';
|
||||||
import TextControl from './TextControl';
|
import TextControl from './TextControl';
|
||||||
import TimeSeriesColumnControl from './TimeSeriesColumnControl';
|
import TimeSeriesColumnControl from './TimeSeriesColumnControl';
|
||||||
@@ -29,6 +30,7 @@ const controlMap = {
|
|||||||
HiddenControl,
|
HiddenControl,
|
||||||
SelectAsyncControl,
|
SelectAsyncControl,
|
||||||
SelectControl,
|
SelectControl,
|
||||||
|
SpatialControl,
|
||||||
TextAreaControl,
|
TextAreaControl,
|
||||||
TextControl,
|
TextControl,
|
||||||
TimeSeriesColumnControl,
|
TimeSeriesColumnControl,
|
||||||
|
|||||||
@@ -482,6 +482,16 @@ export const controls = {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
spatial: {
|
||||||
|
type: 'SpatialControl',
|
||||||
|
label: t('Longitude & Latitude'),
|
||||||
|
validators: [v.nonEmpty],
|
||||||
|
description: t('Point to your spatial columns'),
|
||||||
|
mapStateToProps: state => ({
|
||||||
|
choices: (state.datasource) ? state.datasource.all_cols : [],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
longitude: {
|
longitude: {
|
||||||
type: 'SelectControl',
|
type: 'SelectControl',
|
||||||
label: t('Longitude'),
|
label: t('Longitude'),
|
||||||
|
|||||||
@@ -346,9 +346,8 @@ export const visTypes = {
|
|||||||
label: t('Query'),
|
label: t('Query'),
|
||||||
expanded: true,
|
expanded: true,
|
||||||
controlSetRows: [
|
controlSetRows: [
|
||||||
['longitude', 'latitude'],
|
['spatial', 'size'],
|
||||||
['groupby', 'size'],
|
['groupby', 'row_limit'],
|
||||||
['row_limit'],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -377,9 +376,8 @@ export const visTypes = {
|
|||||||
label: t('Query'),
|
label: t('Query'),
|
||||||
expanded: true,
|
expanded: true,
|
||||||
controlSetRows: [
|
controlSetRows: [
|
||||||
['longitude', 'latitude'],
|
['spatial', 'size'],
|
||||||
['groupby', 'size'],
|
['groupby', 'row_limit'],
|
||||||
['row_limit'],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -408,9 +406,8 @@ export const visTypes = {
|
|||||||
label: t('Query'),
|
label: t('Query'),
|
||||||
expanded: true,
|
expanded: true,
|
||||||
controlSetRows: [
|
controlSetRows: [
|
||||||
['longitude', 'latitude'],
|
['spatial', 'size'],
|
||||||
['groupby', 'size'],
|
['groupby', 'row_limit'],
|
||||||
['row_limit'],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -443,9 +440,8 @@ export const visTypes = {
|
|||||||
label: t('Query'),
|
label: t('Query'),
|
||||||
expanded: true,
|
expanded: true,
|
||||||
controlSetRows: [
|
controlSetRows: [
|
||||||
['longitude', 'latitude'],
|
['spatial', 'size'],
|
||||||
['groupby'],
|
['groupby', 'row_limit'],
|
||||||
['row_limit'],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -470,14 +466,6 @@ export const visTypes = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
controlOverrides: {
|
controlOverrides: {
|
||||||
all_columns_x: {
|
|
||||||
label: t('Longitude Column'),
|
|
||||||
validators: [v.nonEmpty],
|
|
||||||
},
|
|
||||||
all_columns_y: {
|
|
||||||
label: t('Latitude Column'),
|
|
||||||
validators: [v.nonEmpty],
|
|
||||||
},
|
|
||||||
dimension: {
|
dimension: {
|
||||||
label: t('Categorical Color'),
|
label: t('Categorical Color'),
|
||||||
description: t('Pick a dimension from which categorical colors are defined'),
|
description: t('Pick a dimension from which categorical colors are defined'),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "superset",
|
"name": "superset",
|
||||||
"version": "0.21.0dev",
|
"version": "0.21.0",
|
||||||
"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": {
|
||||||
|
|||||||
@@ -249,9 +249,15 @@ table.table-no-hover tr:hover {
|
|||||||
.m-t-5 {
|
.m-t-5 {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
.m-t-10 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
.m-l-5 {
|
.m-l-5 {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
.m-l-10 {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
.m-l-25 {
|
.m-l-25 {
|
||||||
margin-left: 25px;
|
margin-left: 25px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ class SqlaTable(Model, BaseDatasource):
|
|||||||
# For backwards compatibility and edge cases
|
# For backwards compatibility and edge cases
|
||||||
# where a column data type might have changed
|
# where a column data type might have changed
|
||||||
if isinstance(v, basestring):
|
if isinstance(v, basestring):
|
||||||
v = v.strip("'").strip('"')
|
v = v.strip(""").strip(""")
|
||||||
if col_obj.is_num:
|
if col_obj.is_num:
|
||||||
v = utils.string_to_num(v)
|
v = utils.string_to_num(v)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import textwrap
|
|||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from sqlalchemy import BigInteger, Date, DateTime, Float, String
|
from sqlalchemy import BigInteger, Date, DateTime, Float, String
|
||||||
|
import geohash
|
||||||
|
|
||||||
from superset import app, db, utils
|
from superset import app, db, utils
|
||||||
from superset.connectors.connector_registry import ConnectorRegistry
|
from superset.connectors.connector_registry import ConnectorRegistry
|
||||||
@@ -1017,6 +1018,9 @@ def load_long_lat_data():
|
|||||||
pdf['date'] = datetime.datetime.now().date()
|
pdf['date'] = datetime.datetime.now().date()
|
||||||
pdf['occupancy'] = [random.randint(1, 6) for _ in range(len(pdf))]
|
pdf['occupancy'] = [random.randint(1, 6) for _ in range(len(pdf))]
|
||||||
pdf['radius_miles'] = [random.uniform(1, 3) for _ in range(len(pdf))]
|
pdf['radius_miles'] = [random.uniform(1, 3) for _ in range(len(pdf))]
|
||||||
|
pdf['geohash'] = pdf[['LAT', 'LON']].apply(
|
||||||
|
lambda x: geohash.encode(*x), axis=1)
|
||||||
|
pdf['delimited'] = pdf['LAT'].map(str).str.cat(pdf['LON'].map(str), sep=',')
|
||||||
pdf.to_sql( # pylint: disable=no-member
|
pdf.to_sql( # pylint: disable=no-member
|
||||||
'long_lat',
|
'long_lat',
|
||||||
db.engine,
|
db.engine,
|
||||||
@@ -1036,6 +1040,8 @@ def load_long_lat_data():
|
|||||||
'date': Date(),
|
'date': Date(),
|
||||||
'occupancy': Float(),
|
'occupancy': Float(),
|
||||||
'radius_miles': Float(),
|
'radius_miles': Float(),
|
||||||
|
'geohash': String(12),
|
||||||
|
'delimited': String(60),
|
||||||
},
|
},
|
||||||
index=False)
|
index=False)
|
||||||
print("Done loading table!")
|
print("Done loading table!")
|
||||||
@@ -1233,8 +1239,11 @@ def load_deck_dash():
|
|||||||
slices = []
|
slices = []
|
||||||
tbl = db.session.query(TBL).filter_by(table_name='long_lat').first()
|
tbl = db.session.query(TBL).filter_by(table_name='long_lat').first()
|
||||||
slice_data = {
|
slice_data = {
|
||||||
"longitude": "LON",
|
"spatial": {
|
||||||
"latitude": "LAT",
|
"type": "latlong",
|
||||||
|
"lonCol": "LON",
|
||||||
|
"latCol": "LAT",
|
||||||
|
},
|
||||||
"color_picker": {
|
"color_picker": {
|
||||||
"r": 205,
|
"r": 205,
|
||||||
"g": 0,
|
"g": 0,
|
||||||
@@ -1281,8 +1290,11 @@ def load_deck_dash():
|
|||||||
"point_unit": "square_m",
|
"point_unit": "square_m",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"row_limit": 5000,
|
"row_limit": 5000,
|
||||||
"longitude": "LON",
|
"spatial": {
|
||||||
"latitude": "LAT",
|
"type": "latlong",
|
||||||
|
"lonCol": "LON",
|
||||||
|
"latCol": "LAT",
|
||||||
|
},
|
||||||
"mapbox_style": "mapbox://styles/mapbox/dark-v9",
|
"mapbox_style": "mapbox://styles/mapbox/dark-v9",
|
||||||
"granularity_sqla": "date",
|
"granularity_sqla": "date",
|
||||||
"size": "count",
|
"size": "count",
|
||||||
@@ -1290,10 +1302,12 @@ def load_deck_dash():
|
|||||||
"since": "2014-01-01",
|
"since": "2014-01-01",
|
||||||
"point_radius": "Auto",
|
"point_radius": "Auto",
|
||||||
"until": "now",
|
"until": "now",
|
||||||
"color_picker": {"a": 1,
|
"color_picker": {
|
||||||
"r": 14,
|
"a": 1,
|
||||||
"b": 0,
|
"r": 14,
|
||||||
"g": 255},
|
"b": 0,
|
||||||
|
"g": 255,
|
||||||
|
},
|
||||||
"grid_size": 20,
|
"grid_size": 20,
|
||||||
"where": "",
|
"where": "",
|
||||||
"having": "",
|
"having": "",
|
||||||
@@ -1321,10 +1335,13 @@ def load_deck_dash():
|
|||||||
slices.append(slc)
|
slices.append(slc)
|
||||||
|
|
||||||
slice_data = {
|
slice_data = {
|
||||||
|
"spatial": {
|
||||||
|
"type": "latlong",
|
||||||
|
"lonCol": "LON",
|
||||||
|
"latCol": "LAT",
|
||||||
|
},
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"row_limit": 5000,
|
"row_limit": 5000,
|
||||||
"longitude": "LON",
|
|
||||||
"latitude": "LAT",
|
|
||||||
"mapbox_style": "mapbox://styles/mapbox/streets-v9",
|
"mapbox_style": "mapbox://styles/mapbox/streets-v9",
|
||||||
"granularity_sqla": "date",
|
"granularity_sqla": "date",
|
||||||
"size": "count",
|
"size": "count",
|
||||||
@@ -1367,10 +1384,13 @@ def load_deck_dash():
|
|||||||
slices.append(slc)
|
slices.append(slc)
|
||||||
|
|
||||||
slice_data = {
|
slice_data = {
|
||||||
|
"spatial": {
|
||||||
|
"type": "latlong",
|
||||||
|
"lonCol": "LON",
|
||||||
|
"latCol": "LAT",
|
||||||
|
},
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"row_limit": 5000,
|
"row_limit": 5000,
|
||||||
"longitude": "LON",
|
|
||||||
"latitude": "LAT",
|
|
||||||
"mapbox_style": "mapbox://styles/mapbox/satellite-streets-v9",
|
"mapbox_style": "mapbox://styles/mapbox/satellite-streets-v9",
|
||||||
"granularity_sqla": "date",
|
"granularity_sqla": "date",
|
||||||
"size": "count",
|
"size": "count",
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""update_spatial_params
|
||||||
|
|
||||||
|
Revision ID: 67a6ac9b727b
|
||||||
|
Revises: 4736ec66ce19
|
||||||
|
Create Date: 2017-12-08 08:19:21.148775
|
||||||
|
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy import Column, Integer, String, Text
|
||||||
|
|
||||||
|
from superset import db
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '67a6ac9b727b'
|
||||||
|
down_revision = '4736ec66ce19'
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
class Slice(Base):
|
||||||
|
__tablename__ = 'slices'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
viz_type = Column(String(250))
|
||||||
|
params = Column(Text)
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = db.Session(bind=bind)
|
||||||
|
|
||||||
|
for slc in session.query(Slice).filter(Slice.viz_type.like('deck_%')):
|
||||||
|
params = json.loads(slc.params)
|
||||||
|
if params.get('latitude'):
|
||||||
|
params['spatial'] = {
|
||||||
|
'lonCol': params.get('longitude'),
|
||||||
|
'latCol': params.get('latitude'),
|
||||||
|
'type': 'latlong',
|
||||||
|
}
|
||||||
|
del params['latitude']
|
||||||
|
del params['longitude']
|
||||||
|
slc.params = json.dumps(params)
|
||||||
|
session.merge(slc)
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
pass
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{% extends "appbuilder/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div id="loginbox" style="margin-top:50px;" class="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">
|
||||||
|
<center>
|
||||||
|
<a href="/login/google">
|
||||||
|
<img width="300" src="https://developers.google.com/accounts/images/sign-in-with-google.png">
|
||||||
|
</a>
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -1049,9 +1049,9 @@ class Superset(BaseSupersetView):
|
|||||||
status=200,
|
status=200,
|
||||||
mimetype='application/json')
|
mimetype='application/json')
|
||||||
|
|
||||||
|
#@has_access_api # remove until we figure out auth
|
||||||
@log_this
|
@log_this
|
||||||
@has_access_api
|
@expose("/explore_json/<datasource_type>/<datasource_id>/")
|
||||||
@expose('/explore_json/<datasource_type>/<datasource_id>/')
|
|
||||||
def explore_json(self, datasource_type, datasource_id):
|
def explore_json(self, datasource_type, datasource_id):
|
||||||
try:
|
try:
|
||||||
viz_obj = self.get_viz(
|
viz_obj = self.get_viz(
|
||||||
@@ -1064,7 +1064,7 @@ class Superset(BaseSupersetView):
|
|||||||
utils.error_msg_from_exception(e),
|
utils.error_msg_from_exception(e),
|
||||||
stacktrace=traceback.format_exc())
|
stacktrace=traceback.format_exc())
|
||||||
|
|
||||||
if not self.datasource_access(viz_obj.datasource):
|
if not self.datasource_access(viz_obj.datasource) and request.args.get("restful") != "true":
|
||||||
return json_error_response(DATASOURCE_ACCESS_ERR, status=404)
|
return json_error_response(DATASOURCE_ACCESS_ERR, status=404)
|
||||||
|
|
||||||
if request.args.get('csv') == 'true':
|
if request.args.get('csv') == 'true':
|
||||||
@@ -1840,6 +1840,7 @@ class Superset(BaseSupersetView):
|
|||||||
@expose('/dashboard/<dashboard_id>/')
|
@expose('/dashboard/<dashboard_id>/')
|
||||||
def dashboard(self, dashboard_id):
|
def dashboard(self, dashboard_id):
|
||||||
"""Server side rendering for a dashboard"""
|
"""Server side rendering for a dashboard"""
|
||||||
|
logging.info("in dashboard")
|
||||||
session = db.session()
|
session = db.session()
|
||||||
qry = session.query(models.Dashboard)
|
qry = session.query(models.Dashboard)
|
||||||
if dashboard_id.isdigit():
|
if dashboard_id.isdigit():
|
||||||
@@ -1897,6 +1898,61 @@ class Superset(BaseSupersetView):
|
|||||||
bootstrap_data=json.dumps(bootstrap_data),
|
bootstrap_data=json.dumps(bootstrap_data),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@expose("/dashboard_json/<dashboard_id>/")
|
||||||
|
def dashboard_json(self, dashboard_id):
|
||||||
|
"""Server side rendering for a dashboard"""
|
||||||
|
session = db.session()
|
||||||
|
qry = session.query(models.Dashboard)
|
||||||
|
if dashboard_id.isdigit():
|
||||||
|
qry = qry.filter_by(id=int(dashboard_id))
|
||||||
|
else:
|
||||||
|
qry = qry.filter_by(slug=dashboard_id)
|
||||||
|
|
||||||
|
dash = qry.one()
|
||||||
|
datasources = set()
|
||||||
|
for slc in dash.slices:
|
||||||
|
datasource = slc.datasource
|
||||||
|
if datasource:
|
||||||
|
datasources.add(datasource)
|
||||||
|
|
||||||
|
# Commenting until we figure out Authentication from a service
|
||||||
|
# for datasource in datasources:
|
||||||
|
# if datasource and not self.datasource_access(datasource):
|
||||||
|
# flash(
|
||||||
|
# __(get_datasource_access_error_msg(datasource.name)),
|
||||||
|
# "danger")
|
||||||
|
# return redirect(
|
||||||
|
# 'superset/request_access/?'
|
||||||
|
# 'dashboard_id={dash.id}&'.format(**locals()))
|
||||||
|
|
||||||
|
# Hack to log the dashboard_id properly, even when getting a slug
|
||||||
|
@log_this
|
||||||
|
def dashboard(**kwargs): # noqa
|
||||||
|
pass
|
||||||
|
dashboard(dashboard_id=dash.id)
|
||||||
|
|
||||||
|
dash_edit_perm = check_ownership(dash, raise_if_false=False)
|
||||||
|
dash_save_perm = \
|
||||||
|
dash_edit_perm and self.can_access('can_save_dash', 'Superset')
|
||||||
|
|
||||||
|
standalone_mode = request.args.get("standalone") == "true"
|
||||||
|
|
||||||
|
dashboard_data = dash.data
|
||||||
|
dashboard_data.update({
|
||||||
|
'standalone_mode': standalone_mode,
|
||||||
|
'dash_save_perm': dash_save_perm,
|
||||||
|
'dash_edit_perm': dash_edit_perm,
|
||||||
|
})
|
||||||
|
|
||||||
|
bootstrap_data = {
|
||||||
|
'user_id': g.user.get_id(),
|
||||||
|
'dashboard_data': dashboard_data,
|
||||||
|
'datasources': {ds.uid: ds.data for ds in datasources},
|
||||||
|
'common': self.common_bootsrap_payload(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_success(json.dumps(bootstrap_data))
|
||||||
|
|
||||||
@has_access
|
@has_access
|
||||||
@expose('/sync_druid/', methods=['POST'])
|
@expose('/sync_druid/', methods=['POST'])
|
||||||
@log_this
|
@log_this
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import zlib
|
|||||||
from dateutil import relativedelta as rdelta
|
from dateutil import relativedelta as rdelta
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_babel import lazy_gettext as _
|
from flask_babel import lazy_gettext as _
|
||||||
|
import geohash
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -1799,22 +1800,56 @@ class BaseDeckGLViz(BaseViz):
|
|||||||
|
|
||||||
def get_position(self, d):
|
def get_position(self, d):
|
||||||
return [
|
return [
|
||||||
d.get(self.form_data.get('longitude')),
|
d.get('lon'),
|
||||||
d.get(self.form_data.get('latitude')),
|
d.get('lat'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def query_obj(self):
|
def query_obj(self):
|
||||||
d = super(BaseDeckGLViz, self).query_obj()
|
d = super(BaseDeckGLViz, self).query_obj()
|
||||||
fd = self.form_data
|
fd = self.form_data
|
||||||
|
|
||||||
d['groupby'] = [fd.get('longitude'), fd.get('latitude')]
|
gb = []
|
||||||
|
|
||||||
|
spatial = fd.get('spatial')
|
||||||
|
if spatial.get('type') == 'latlong':
|
||||||
|
gb += [spatial.get('lonCol')]
|
||||||
|
gb += [spatial.get('latCol')]
|
||||||
|
elif spatial.get('type') == 'delimited':
|
||||||
|
gb += [spatial.get('lonlatCol')]
|
||||||
|
elif spatial.get('type') == 'geohash':
|
||||||
|
gb += [spatial.get('geohashCol')]
|
||||||
|
|
||||||
if fd.get('dimension'):
|
if fd.get('dimension'):
|
||||||
d['groupby'] += [fd.get('dimension')]
|
d['groupby'] += [fd.get('dimension')]
|
||||||
|
|
||||||
|
d['groupby'] = gb
|
||||||
d['metrics'] = self.get_metrics()
|
d['metrics'] = self.get_metrics()
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def get_data(self, df):
|
def get_data(self, df):
|
||||||
|
fd = self.form_data
|
||||||
|
spatial = fd.get('spatial')
|
||||||
|
if spatial.get('type') == 'latlong':
|
||||||
|
df = df.rename(columns={
|
||||||
|
spatial.get('lonCol'): 'lon',
|
||||||
|
spatial.get('latCol'): 'lat'})
|
||||||
|
elif spatial.get('type') == 'delimited':
|
||||||
|
cols = ['lon', 'lat']
|
||||||
|
if spatial.get('reverseCheckbox'):
|
||||||
|
cols.reverse()
|
||||||
|
df[cols] = (
|
||||||
|
df[spatial.get('lonlatCol')]
|
||||||
|
.str
|
||||||
|
.split(spatial.get('delimiter'), expand=True)
|
||||||
|
.astype(np.float64)
|
||||||
|
)
|
||||||
|
del df[spatial.get('lonlatCol')]
|
||||||
|
elif spatial.get('type') == 'geohash':
|
||||||
|
latlong = df[spatial.get('geohashCol')].map(geohash.decode)
|
||||||
|
df['lat'] = latlong.apply(lambda x: x[0])
|
||||||
|
df['lon'] = latlong.apply(lambda x: x[1])
|
||||||
|
del df['geohash']
|
||||||
|
|
||||||
features = []
|
features = []
|
||||||
for d in df.to_dict(orient='records'):
|
for d in df.to_dict(orient='records'):
|
||||||
d = dict(position=self.get_position(d), **self.get_properties(d))
|
d = dict(position=self.get_position(d), **self.get_properties(d))
|
||||||
|
|||||||
Reference in New Issue
Block a user