mirror of https://github.com/apache/superset.git
Humanized time boundaries and granularity
This commit is contained in:
parent
16f7bf9054
commit
8b175638cd
|
@ -21,7 +21,9 @@ class Datasource(Model, AuditMixin):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def metrics_combo(self):
|
def metrics_combo(self):
|
||||||
return [(m.metric_name, m.verbose_name) for m in self.metrics]
|
return sorted(
|
||||||
|
[(m.metric_name, m.verbose_name) for m in self.metrics],
|
||||||
|
key=lambda x: x[1])
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.datasource_name
|
return self.datasource_name
|
||||||
|
|
|
@ -2,13 +2,9 @@
|
||||||
{% block head_css %}
|
{% block head_css %}
|
||||||
{{super()}}
|
{{super()}}
|
||||||
<style>
|
<style>
|
||||||
form .row {
|
.select2-container-multi .select2-choices {
|
||||||
margin-left: 0;
|
height: 70px;
|
||||||
margin-right: 0;
|
overflow: auto;
|
||||||
}
|
|
||||||
form .col {
|
|
||||||
padding-right:0px;
|
|
||||||
padding-left:0px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -24,8 +20,13 @@ form .col {
|
||||||
<form method="GET">
|
<form method="GET">
|
||||||
<div>{{ form.viz_type.label }}: {{ form.viz_type(class_="form-control select2") }}</div>
|
<div>{{ form.viz_type.label }}: {{ form.viz_type(class_="form-control select2") }}</div>
|
||||||
<div>{{ form.metric.label }}: {{ form.metric(class_="form-control select2") }}</div>
|
<div>{{ form.metric.label }}: {{ form.metric(class_="form-control select2") }}</div>
|
||||||
<div>{{ form.granularity.label }}: {{ form.granularity(class_="form-control select2") }}</div>
|
<div>{{ form.granularity.label }}: {{ form.granularity(class_="form-control select2_free_granularity") }}</div>
|
||||||
<div>{{ form.since.label }}: {{ form.since(class_="form-control select2") }}</div>
|
<div class="row">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-xs-6">{{ form.since.label }}: {{ form.since(class_="form-control select2_free_since") }}</div>
|
||||||
|
<div class="col-xs-6">{{ form.until.label }}: {{ form.until(class_="form-control select2_free_until") }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>{{ form.groupby.label }}: {{ form.groupby(class_="form-control select2") }}</div>
|
<div>{{ form.groupby.label }}: {{ form.groupby(class_="form-control select2") }}</div>
|
||||||
<div>{{ form.limit.label }}: {{ form.limit(class_="form-control select2") }}</div>
|
<div>{{ form.limit.label }}: {{ form.limit(class_="form-control select2") }}</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -33,9 +34,7 @@ form .col {
|
||||||
<div id="filters">
|
<div id="filters">
|
||||||
{% for i in range(10) %}
|
{% for i in range(10) %}
|
||||||
<div id="flt{{ i }}" class="{{ "hidden" if i != 1 }}">
|
<div id="flt{{ i }}" class="{{ "hidden" if i != 1 }}">
|
||||||
<div class="row">
|
|
||||||
<span class="" style="width: 100px;">{{ form['flt_col_' ~ i](class_="form-control select2 inc") }}</span>
|
<span class="" style="width: 100px;">{{ form['flt_col_' ~ i](class_="form-control select2 inc") }}</span>
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="col col-md-3">{{ form['flt_op_' ~ i](class_="form-control select2 input-sm inc") }}</span>
|
<span class="col col-md-3">{{ form['flt_op_' ~ i](class_="form-control select2 input-sm inc") }}</span>
|
||||||
<span class="col col-md-7">{{ form['flt_eq_' ~ i](class_="form-control inc") }}</span>
|
<span class="col col-md-7">{{ form['flt_eq_' ~ i](class_="form-control inc") }}</span>
|
||||||
|
@ -83,7 +82,51 @@ form .col {
|
||||||
<script>
|
<script>
|
||||||
$( document ).ready(function() {
|
$( document ).ready(function() {
|
||||||
$(".select2").select2();
|
$(".select2").select2();
|
||||||
$(".select2_tags").select2({tags: true});
|
|
||||||
|
function create_choices (term, data) {
|
||||||
|
if ($(data).filter(function() {
|
||||||
|
return this.text.localeCompare(term)===0;
|
||||||
|
}).length===0)
|
||||||
|
{return {id:term, text:term};}
|
||||||
|
}
|
||||||
|
$(".select2_free_since").select2({
|
||||||
|
createSearchChoice: create_choices,
|
||||||
|
multiple: false,
|
||||||
|
data: [
|
||||||
|
{id: '-1 hour', text: '-1 hour'},
|
||||||
|
{id: '-12 hours', text: '-12 hours'},
|
||||||
|
{id: '-1 day', text: '-1 day'},
|
||||||
|
{id: '-7 days', text: '-7 days'},
|
||||||
|
{id: '-28 days', text: '-28 days'},
|
||||||
|
{id: '-90 days', text: '-90 days'},
|
||||||
|
{id: '{{ form.data.since }}', text: '{{ form.data.since }}'},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
$(".select2_free_until").select2({
|
||||||
|
createSearchChoice: create_choices,
|
||||||
|
multiple: false,
|
||||||
|
data: [
|
||||||
|
{id: '{{ form.data.until }}', text: '{{ form.data.until }}'},
|
||||||
|
{id: 'now', text: 'now'},
|
||||||
|
{id: '-1 day', text: '-1 day'},
|
||||||
|
{id: '-7 days', text: '-7 days'},
|
||||||
|
{id: '-28 days', text: '-28 days'},
|
||||||
|
{id: '-90 days', text: '-90 days'},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
$(".select2_free_granularity").select2({
|
||||||
|
createSearchChoice: create_choices,
|
||||||
|
multiple: false,
|
||||||
|
data: [
|
||||||
|
{id: '{{ form.data.granularity }}', text: '{{ form.data.granularity }}'},
|
||||||
|
{id: '5 seconds', text: '5 seconds'},
|
||||||
|
{id: '30 seconds', text: '30 seconds'},
|
||||||
|
{id: '1 minute', text: '1 minute'},
|
||||||
|
{id: '5 minutes', text: '5 minutes'},
|
||||||
|
{id: '1 day', text: '1 day'},
|
||||||
|
{id: '7 days', text: '7 days'},
|
||||||
|
]
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
28
app/utils.py
28
app/utils.py
|
@ -2,13 +2,6 @@ import config
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
import parsedatetime
|
import parsedatetime
|
||||||
|
|
||||||
since_l = {
|
|
||||||
'1hour': timedelta(hours=1),
|
|
||||||
'1day': timedelta(days=1),
|
|
||||||
'7days': timedelta(days=7),
|
|
||||||
'28days': timedelta(days=28),
|
|
||||||
'all': timedelta(days=365*100)
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_pydruid_client():
|
def get_pydruid_client():
|
||||||
from pydruid import client
|
from pydruid import client
|
||||||
|
@ -26,6 +19,25 @@ def parse_human_datetime(s):
|
||||||
True
|
True
|
||||||
"""
|
"""
|
||||||
cal = parsedatetime.Calendar()
|
cal = parsedatetime.Calendar()
|
||||||
d = cal.parse(s)[0]
|
return dttm_from_timtuple(cal.parse(s)[0])
|
||||||
|
|
||||||
|
|
||||||
|
def dttm_from_timtuple(d):
|
||||||
return datetime(
|
return datetime(
|
||||||
d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec)
|
d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_human_timedelta(s):
|
||||||
|
"""
|
||||||
|
Use the parsedatetime lib to return ``datetime.datetime`` from human
|
||||||
|
generated strings
|
||||||
|
|
||||||
|
>>> parse_human_datetime("now") <= datetime.now()
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
cal = parsedatetime.Calendar()
|
||||||
|
dttm = dttm_from_timtuple(datetime.now().timetuple())
|
||||||
|
d = cal.parse(s, dttm)[0]
|
||||||
|
d = datetime(
|
||||||
|
d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec)
|
||||||
|
return d - dttm
|
||||||
|
|
14
app/views.py
14
app/views.py
|
@ -9,6 +9,7 @@ import config
|
||||||
from wtforms import Form, SelectMultipleField, SelectField, TextField
|
from wtforms import Form, SelectMultipleField, SelectField, TextField
|
||||||
from wtforms.fields import Field
|
from wtforms.fields import Field
|
||||||
|
|
||||||
|
|
||||||
class OmgWtForm(Form):
|
class OmgWtForm(Form):
|
||||||
field_order = (
|
field_order = (
|
||||||
'viz_type', 'granularity', 'since', 'group_by', 'limit')
|
'viz_type', 'granularity', 'since', 'group_by', 'limit')
|
||||||
|
@ -44,11 +45,14 @@ def form_factory(datasource, form_args=None):
|
||||||
groupby = SelectMultipleField(
|
groupby = SelectMultipleField(
|
||||||
'Group by', choices=[
|
'Group by', choices=[
|
||||||
(s, s) for s in datasource.groupby_column_names])
|
(s, s) for s in datasource.groupby_column_names])
|
||||||
granularity = SelectField(
|
#granularity = SelectField(
|
||||||
'Time Granularity', choices=[(g, g) for g in grain])
|
# 'Time Granularity', choices=[(g, g) for g in grain])
|
||||||
since = SelectField(
|
#since = SelectField(
|
||||||
'Since', choices=[(s, s) for s in utils.since_l.keys()],
|
# 'Since', choices=[(s, s) for s in utils.since_l.keys()],
|
||||||
default="all")
|
# default="all")
|
||||||
|
granularity = TextField('Time Granularity', default="one day")
|
||||||
|
since = TextField('Since', default="one day ago")
|
||||||
|
until = TextField('Until', default="now")
|
||||||
limit = SelectField(
|
limit = SelectField(
|
||||||
'Limit', choices=[(s, s) for s in limits])
|
'Limit', choices=[(s, s) for s in limits])
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
|
|
19
app/viz.py
19
app/viz.py
|
@ -66,19 +66,28 @@ class BaseViz(object):
|
||||||
ds = self.datasource
|
ds = self.datasource
|
||||||
args = self.form_data
|
args = self.form_data
|
||||||
groupby = args.getlist("groupby") or []
|
groupby = args.getlist("groupby") or []
|
||||||
granularity = args.get("granularity")
|
granularity = args.get("granularity", "1 day")
|
||||||
|
granularity = utils.parse_human_timedelta(granularity).total_seconds() * 1000
|
||||||
aggregations = {
|
aggregations = {
|
||||||
m.metric_name: m.json_obj
|
m.metric_name: m.json_obj
|
||||||
for m in ds.metrics if m.metric_name == self.metric
|
for m in ds.metrics if m.metric_name == self.metric
|
||||||
}
|
}
|
||||||
limit = int(
|
limit = int(
|
||||||
args.get("limit", config.ROW_LIMIT)) or config.ROW_LIMIT
|
args.get("limit", config.ROW_LIMIT)) or config.ROW_LIMIT
|
||||||
since = args.get("since", "all")
|
since = args.get("since", "1 year ago")
|
||||||
from_dttm = (datetime.now() - utils.since_l[since]).isoformat()
|
from_dttm = utils.parse_human_datetime(since)
|
||||||
|
if from_dttm > datetime.now():
|
||||||
|
from_dttm = datetime.now() - (from_dttm-datetime.now())
|
||||||
|
from_dttm = from_dttm.isoformat()
|
||||||
|
until = args.get("until", "now")
|
||||||
|
to_dttm = utils.parse_human_datetime(until).isoformat()
|
||||||
|
if from_dttm >= to_dttm:
|
||||||
|
flash("The date range doesn't seem right.", "danger")
|
||||||
|
from_dttm = to_dttm # Making them identicial to not raise
|
||||||
d = {
|
d = {
|
||||||
'datasource': ds.datasource_name,
|
'datasource': ds.datasource_name,
|
||||||
'granularity': granularity or 'all',
|
'granularity': {"type": "duration", "duration": granularity},
|
||||||
'intervals': from_dttm + '/' + datetime.now().isoformat(),
|
'intervals': from_dttm + '/' + to_dttm,
|
||||||
'dimensions': groupby,
|
'dimensions': groupby,
|
||||||
'aggregations': aggregations,
|
'aggregations': aggregations,
|
||||||
'limit_spec': {
|
'limit_spec': {
|
||||||
|
|
|
@ -104,7 +104,7 @@ IMG_UPLOAD_URL = '/static/uploads/'
|
||||||
# Theme configuration
|
# Theme configuration
|
||||||
# these are located on static/appbuilder/css/themes
|
# these are located on static/appbuilder/css/themes
|
||||||
# you can create your own and easily use them placing them on the same dir structure to override
|
# you can create your own and easily use them placing them on the same dir structure to override
|
||||||
APP_THEME = "bootstrap-theme.css" # default bootstrap
|
#APP_THEME = "bootstrap-theme.css" # default bootstrap
|
||||||
#APP_THEME = "cerulean.css"
|
#APP_THEME = "cerulean.css"
|
||||||
#APP_THEME = "amelia.css"
|
#APP_THEME = "amelia.css"
|
||||||
#APP_THEME = "cosmo.css"
|
#APP_THEME = "cosmo.css"
|
||||||
|
|
Loading…
Reference in New Issue