mirror of https://github.com/apache/superset.git
Introducing field sets
This commit is contained in:
parent
6f2c953a9e
commit
b666508085
|
@ -38,18 +38,16 @@ class BetterSelectMultipleField(SelectMultipleField):
|
||||||
|
|
||||||
|
|
||||||
class OmgWtForm(Form):
|
class OmgWtForm(Form):
|
||||||
field_order = tuple()
|
fieldsets = {}
|
||||||
css_classes = dict()
|
css_classes = dict()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fields(self):
|
def form_fields(self):
|
||||||
fields = []
|
all_field_ids = set()
|
||||||
for field in self.field_order:
|
for k, fieldset in self.fieldsets:
|
||||||
if hasattr(self, field):
|
all_field_ids |= fieldset
|
||||||
obj = getattr(self, field)
|
return all_field_ids
|
||||||
if isinstance(obj, Field):
|
|
||||||
fields.append(getattr(self, field))
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def get_field(self, fieldname):
|
def get_field(self, fieldname):
|
||||||
return getattr(self, fieldname)
|
return getattr(self, fieldname)
|
||||||
|
@ -179,7 +177,7 @@ class FormFactory(object):
|
||||||
default="random",
|
default="random",
|
||||||
description="Rotation to apply to words in the cloud"),
|
description="Rotation to apply to words in the cloud"),
|
||||||
'line_interpolation': SelectField(
|
'line_interpolation': SelectField(
|
||||||
"Line Interpolation",
|
"Line Style",
|
||||||
choices=self.choicify([
|
choices=self.choicify([
|
||||||
'linear', 'basis', 'cardinal', 'monotone',
|
'linear', 'basis', 'cardinal', 'monotone',
|
||||||
'step-before', 'step-after']),
|
'step-before', 'step-after']),
|
||||||
|
@ -204,7 +202,7 @@ class FormFactory(object):
|
||||||
default="150",
|
default="150",
|
||||||
description="Font size for the biggest value in the list"),
|
description="Font size for the biggest value in the list"),
|
||||||
'show_brush': BetterBooleanField(
|
'show_brush': BetterBooleanField(
|
||||||
"Range Selector", default=True,
|
"Range Filter", default=True,
|
||||||
description=(
|
description=(
|
||||||
"Whether to display the time range interactive selector")),
|
"Whether to display the time range interactive selector")),
|
||||||
'show_legend': BetterBooleanField(
|
'show_legend': BetterBooleanField(
|
||||||
|
@ -239,7 +237,7 @@ class FormFactory(object):
|
||||||
"[integer] Number of period to compare against, "
|
"[integer] Number of period to compare against, "
|
||||||
"this is relative to the granularity selected")),
|
"this is relative to the granularity selected")),
|
||||||
'time_compare': TextField(
|
'time_compare': TextField(
|
||||||
"Time Shift Compare",
|
"Time Shift",
|
||||||
default="",
|
default="",
|
||||||
description=(
|
description=(
|
||||||
"Overlay a timeseries from a "
|
"Overlay a timeseries from a "
|
||||||
|
@ -278,7 +276,7 @@ class FormFactory(object):
|
||||||
field_css_classes[field] += ['select2Sortable']
|
field_css_classes[field] += ['select2Sortable']
|
||||||
|
|
||||||
class QueryForm(OmgWtForm):
|
class QueryForm(OmgWtForm):
|
||||||
field_order = copy(viz.form_fields)
|
fieldsets = copy(viz.fieldsetizer())
|
||||||
css_classes = field_css_classes
|
css_classes = field_css_classes
|
||||||
standalone = HiddenField()
|
standalone = HiddenField()
|
||||||
async = HiddenField()
|
async = HiddenField()
|
||||||
|
@ -286,6 +284,7 @@ class FormFactory(object):
|
||||||
slice_id = HiddenField()
|
slice_id = HiddenField()
|
||||||
slice_name = HiddenField()
|
slice_name = HiddenField()
|
||||||
previous_viz_type = HiddenField(default=viz.viz_type)
|
previous_viz_type = HiddenField(default=viz.viz_type)
|
||||||
|
collapsed_fieldsets = HiddenField()
|
||||||
|
|
||||||
filter_cols = datasource.filterable_column_names or ['']
|
filter_cols = datasource.filterable_column_names or ['']
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
|
@ -300,16 +299,18 @@ class FormFactory(object):
|
||||||
setattr(
|
setattr(
|
||||||
QueryForm, 'flt_eq_' + str(i),
|
QueryForm, 'flt_eq_' + str(i),
|
||||||
TextField("Super", default=''))
|
TextField("Super", default=''))
|
||||||
for ff in viz.form_fields:
|
for fieldset in viz.fieldsetizer():
|
||||||
if isinstance(ff, string_types):
|
for ff in fieldset['fields']:
|
||||||
ff = [ff]
|
if isinstance(ff, string_types):
|
||||||
for s in ff:
|
ff = [ff]
|
||||||
if s:
|
for s in ff:
|
||||||
setattr(QueryForm, s, px_form_fields[s])
|
if s:
|
||||||
|
setattr(QueryForm, s, px_form_fields[s])
|
||||||
|
|
||||||
# datasource type specific form elements
|
# datasource type specific form elements
|
||||||
if datasource.__class__.__name__ == 'SqlaTable':
|
if datasource.__class__.__name__ == 'SqlaTable':
|
||||||
QueryForm.field_order += ['where', 'having']
|
QueryForm.fieldsets += (
|
||||||
|
{'label': 'SQL', 'fields': ['where', 'having']},)
|
||||||
setattr(QueryForm, 'where', px_form_fields['where'])
|
setattr(QueryForm, 'where', px_form_fields['where'])
|
||||||
setattr(QueryForm, 'having', px_form_fields['having'])
|
setattr(QueryForm, 'having', px_form_fields['having'])
|
||||||
|
|
||||||
|
@ -318,5 +319,4 @@ class FormFactory(object):
|
||||||
QueryForm,
|
QueryForm,
|
||||||
'granularity', px_form_fields['granularity_sqla'])
|
'granularity', px_form_fields['granularity_sqla'])
|
||||||
field_css_classes['granularity'] = ['form-control', 'select2']
|
field_css_classes['granularity'] = ['form-control', 'select2']
|
||||||
|
|
||||||
return QueryForm
|
return QueryForm
|
||||||
|
|
|
@ -10,6 +10,34 @@ form div {
|
||||||
.navbar-brand a {
|
.navbar-brand a {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
fieldset.fs-style {
|
||||||
|
font-family: Verdana, Arial, sans-serif;
|
||||||
|
font-size: small;
|
||||||
|
font-weight: normal;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
background-color: #F4F4F4;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0px;
|
||||||
|
}
|
||||||
|
legend.legend-style {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0px 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0px;
|
||||||
|
color: #444;
|
||||||
|
background-color: transparent;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.nvtooltip{
|
||||||
|
position: relative; !important
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
width: auto;
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
.navbar {
|
.navbar {
|
||||||
-webkit-box-shadow: 0px 3px 3px #AAA;
|
-webkit-box-shadow: 0px 3px 3px #AAA;
|
||||||
-moz-box-shadow: 0px 3px 3px #AAA;
|
-moz-box-shadow: 0px 3px 3px #AAA;
|
||||||
|
|
|
@ -53,6 +53,9 @@ function viz_nvd3(data_attribute) {
|
||||||
// To alter the tooltip header
|
// To alter the tooltip header
|
||||||
// chart.interactiveLayer.tooltip.headerFormatter(function(){return '';});
|
// chart.interactiveLayer.tooltip.headerFormatter(function(){return '';});
|
||||||
chart.xScale(d3.time.scale.utc());
|
chart.xScale(d3.time.scale.utc());
|
||||||
|
chart.useInteractiveGuideline(false);
|
||||||
|
chart.interactiveLayer.tooltip.chartContainer(document.body);
|
||||||
|
|
||||||
chart.interpolate(viz.form_data.line_interpolation);
|
chart.interpolate(viz.form_data.line_interpolation);
|
||||||
chart.xAxis
|
chart.xAxis
|
||||||
.showMaxMin(viz.form_data.x_axis_showminmax)
|
.showMaxMin(viz.form_data.x_axis_showminmax)
|
||||||
|
|
|
@ -22,57 +22,74 @@
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<form id="query" method="GET" style="display: none;">
|
<form id="query" method="GET" style="display: none;">
|
||||||
{% for fieldname in form.field_order %}
|
{% for fieldset in form.fieldsets %}
|
||||||
{% if not fieldname.__iter__ %}
|
<fieldset class="fs-style">
|
||||||
<div>
|
{% if fieldset.label %}
|
||||||
{% set field = form.get_field(fieldname)%}
|
<legend class="legend-style">
|
||||||
<div>
|
<span class="legend_label">{{ fieldset.label }}</span>
|
||||||
{{ field.label }}
|
<span class="collapser"> [-]</span>
|
||||||
{% if field.description %}
|
</legend>
|
||||||
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
|
{% endif %}
|
||||||
title="{{ field.description }}"></i>
|
<div class="fieldset_content">
|
||||||
{% endif %}:
|
{% for fieldname in fieldset.fields %}
|
||||||
{{ field(class_=form.field_css_classes(field.name)) }}
|
{% if not fieldname.__iter__ %}
|
||||||
</div>
|
<div>
|
||||||
</div>
|
{% set field = form.get_field(fieldname)%}
|
||||||
{% else %}
|
<div>
|
||||||
<div class="row">
|
{{ field.label }}
|
||||||
<div class="form-group">
|
{% if field.description %}
|
||||||
{% for name in fieldname %}
|
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
|
||||||
<div class="col-xs-{{ (12 / fieldname|length) | int }}">
|
title="{{ field.description }}"></i>
|
||||||
{% if name %}
|
{% endif %}
|
||||||
{% set field = form.get_field(name)%}
|
{{ field(class_=form.field_css_classes(field.name)) }}
|
||||||
{{ field.label }}
|
</div>
|
||||||
{% if field.description %}
|
</div>
|
||||||
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
|
{% else %}
|
||||||
title="{{ field.description }}"></i>
|
<div class="row">
|
||||||
{% endif %}:
|
<div class="form-group">
|
||||||
{{ field(class_=form.field_css_classes(field.name)) }}
|
{% for name in fieldname %}
|
||||||
|
<div class="col-xs-{{ (12 / fieldname|length) | int }}">
|
||||||
|
{% if name %}
|
||||||
|
{% set field = form.get_field(name)%}
|
||||||
|
{{ field.label }}
|
||||||
|
{% if field.description %}
|
||||||
|
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
|
||||||
|
title="{{ field.description }}"></i>
|
||||||
|
{% endif %}
|
||||||
|
{{ field(class_=form.field_css_classes(field.name)) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</fieldset>
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<fieldset class="fs-style">
|
||||||
<h4>Filters</h4>
|
<legend class="legend-style">
|
||||||
<div id="flt0" style="display: none;">
|
<span class="legend_label">Filters</span>
|
||||||
<span class="">{{ form.flt_col_0(class_="form-control inc") }}</span>
|
<span class="collapser"> [-]</span>
|
||||||
<div class="row">
|
</legend>
|
||||||
<span class="col col-sm-4">{{ form.flt_op_0(class_="form-control inc") }}</span>
|
<div class="fieldset_content">
|
||||||
<span class="col col-sm-6">{{ form.flt_eq_0(class_="form-control inc") }}</span>
|
<div id="flt0" style="display: none;">
|
||||||
<button type="button" class="btn btn-sm remove" aria-label="Delete filter">
|
<span class="">{{ form.flt_col_0(class_="form-control inc") }}</span>
|
||||||
<span class="glyphicon glyphicon-minus" aria-hidden="true"></span>
|
<div class="row">
|
||||||
|
<span class="col col-sm-4">{{ form.flt_op_0(class_="form-control inc") }}</span>
|
||||||
|
<span class="col col-sm-6">{{ form.flt_eq_0(class_="form-control inc") }}</span>
|
||||||
|
<button type="button" class="btn btn-sm remove" aria-label="Delete filter">
|
||||||
|
<span class="glyphicon glyphicon-minus" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<hr style="margin: 5px 0px;"/>
|
||||||
|
</div>
|
||||||
|
<div id="filters"></div>
|
||||||
|
<button type="button" id="plus" class="btn btn-sm" aria-label="Add a filter">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<hr style="margin: 5px 0px;"/>
|
</fieldset>
|
||||||
</div>
|
|
||||||
<div id="filters"></div>
|
|
||||||
<button type="button" id="plus" class="btn btn-sm" aria-label="Add a filter">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
</button>
|
|
||||||
<hr>
|
|
||||||
<button type="button" class="btn btn-primary druidify">
|
<button type="button" class="btn btn-primary druidify">
|
||||||
<i class="fa fa-bolt"></i>
|
<i class="fa fa-bolt"></i>
|
||||||
Slice!
|
Slice!
|
||||||
|
@ -91,6 +108,7 @@
|
||||||
<img src="{{ url_for("static", filename="img/tux_panoramix.png") }}" width=250>
|
<img src="{{ url_for("static", filename="img/tux_panoramix.png") }}" width=250>
|
||||||
{{ form.slice_id() }}
|
{{ form.slice_id() }}
|
||||||
{{ form.slice_name() }}
|
{{ form.slice_name() }}
|
||||||
|
{{ form.collapsed_fieldsets() }}
|
||||||
<input type="hidden" name="action" id="action" value="">
|
<input type="hidden" name="action" id="action" value="">
|
||||||
<input type="hidden" name="datasource_name" value="{{ datasource.name }}">
|
<input type="hidden" name="datasource_name" value="{{ datasource.name }}">
|
||||||
<input type="hidden" name="datasource_id" value="{{ datasource.id }}">
|
<input type="hidden" name="datasource_id" value="{{ datasource.id }}">
|
||||||
|
@ -161,6 +179,57 @@
|
||||||
var widget = px.initializeWidget(data);
|
var widget = px.initializeWidget(data);
|
||||||
$('.widget').data('widget', widget);
|
$('.widget').data('widget', widget);
|
||||||
widget.render();
|
widget.render();
|
||||||
|
|
||||||
|
function get_collapsed_fieldsets(){
|
||||||
|
collapsed_fieldsets = $("#collapsed_fieldsets").val()
|
||||||
|
if (collapsed_fieldsets != undefined && collapsed_fieldsets != "")
|
||||||
|
collapsed_fieldsets = collapsed_fieldsets.split('||');
|
||||||
|
else
|
||||||
|
collapsed_fieldsets = [];
|
||||||
|
return collapsed_fieldsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle_fieldset(legend, animation) {
|
||||||
|
var parent = legend.parent();
|
||||||
|
fieldset = parent.find(".legend_label").text();
|
||||||
|
collapsed_fieldsets = get_collapsed_fieldsets();
|
||||||
|
|
||||||
|
if (!parent.hasClass("collapsed")){
|
||||||
|
if (animation)
|
||||||
|
parent.find(".fieldset_content").slideUp();
|
||||||
|
else
|
||||||
|
parent.find(".fieldset_content").hide();
|
||||||
|
|
||||||
|
parent.addClass("collapsed");
|
||||||
|
parent.find("span.collapser").text("[+]");
|
||||||
|
var index = collapsed_fieldsets.indexOf(fieldset);
|
||||||
|
if (index === -1 && fieldset !== "" && fieldset !== undefined) {
|
||||||
|
collapsed_fieldsets.push(fieldset);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (animation)
|
||||||
|
parent.find(".fieldset_content").slideDown();
|
||||||
|
else
|
||||||
|
parent.find(".fieldset_content").show();
|
||||||
|
parent.removeClass("collapsed");
|
||||||
|
parent.find("span.collapser").text("[-]");
|
||||||
|
|
||||||
|
// removing from array, js is overcomplicated
|
||||||
|
var index = collapsed_fieldsets.indexOf(fieldset);
|
||||||
|
if (index !== -1) {
|
||||||
|
collapsed_fieldsets.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#collapsed_fieldsets").val(collapsed_fieldsets.join("||"));
|
||||||
|
}
|
||||||
|
|
||||||
|
$('legend').click(function () {
|
||||||
|
toggle_fieldset($(this), true);
|
||||||
|
});
|
||||||
|
collapsed_fieldsets = get_collapsed_fieldsets();
|
||||||
|
for(var i=0; i<collapsed_fieldsets.length;i++){
|
||||||
|
toggle_fieldset($('legend:contains("' + collapsed_fieldsets[i] + '")'), false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -335,7 +335,7 @@ class Panoramix(BaseView):
|
||||||
d = request.args.to_dict(flat=False)
|
d = request.args.to_dict(flat=False)
|
||||||
del d['action']
|
del d['action']
|
||||||
del d['previous_viz_type']
|
del d['previous_viz_type']
|
||||||
as_list = ('metrics', 'groupby')
|
as_list = ('metrics', 'groupby', 'columns')
|
||||||
for k in d:
|
for k in d:
|
||||||
v = d.get(k)
|
v = d.get(k)
|
||||||
if k in as_list and not isinstance(v, list):
|
if k in as_list and not isinstance(v, list):
|
||||||
|
|
|
@ -70,6 +70,17 @@ class BaseViz(object):
|
||||||
self.groupby = self.form_data.get('groupby') or []
|
self.groupby = self.form_data.get('groupby') or []
|
||||||
self.reassignments()
|
self.reassignments()
|
||||||
|
|
||||||
|
def fieldsetizer(self):
|
||||||
|
"""
|
||||||
|
Makes form_fields support either a list approach or a fieldsets
|
||||||
|
approach
|
||||||
|
"""
|
||||||
|
ff = self.fieldsets if hasattr(self, 'fieldsets') else self.form_fields
|
||||||
|
if isinstance(ff[0], dict):
|
||||||
|
return self.fieldsets
|
||||||
|
else:
|
||||||
|
return ({'label': None, 'fields': ff},)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def flat_form_fields(cls):
|
def flat_form_fields(cls):
|
||||||
l = []
|
l = []
|
||||||
|
@ -246,6 +257,10 @@ class PivotTableViz(BaseViz):
|
||||||
groupby = self.form_data.get('groupby')
|
groupby = self.form_data.get('groupby')
|
||||||
columns = self.form_data.get('columns')
|
columns = self.form_data.get('columns')
|
||||||
metrics = self.form_data.get('metrics')
|
metrics = self.form_data.get('metrics')
|
||||||
|
if not columns:
|
||||||
|
columns = []
|
||||||
|
if not groupby:
|
||||||
|
groupby = []
|
||||||
if not groupby:
|
if not groupby:
|
||||||
raise Exception("Please choose at least one \"Group by\" field ")
|
raise Exception("Please choose at least one \"Group by\" field ")
|
||||||
if not metrics:
|
if not metrics:
|
||||||
|
@ -255,7 +270,7 @@ class PivotTableViz(BaseViz):
|
||||||
any(v in columns for v in groupby)):
|
any(v in columns for v in groupby)):
|
||||||
raise Exception("groupby and columns can't overlap")
|
raise Exception("groupby and columns can't overlap")
|
||||||
|
|
||||||
d['groupby'] = list(set(d['groupby']) | set(self.form_data.get('columns')))
|
d['groupby'] = list(set(groupby) | set(columns))
|
||||||
d['is_timeseries'] = False
|
d['is_timeseries'] = False
|
||||||
d['timeseries_limit'] = None
|
d['timeseries_limit'] = None
|
||||||
return d
|
return d
|
||||||
|
@ -464,19 +479,33 @@ class NVD3TimeSeriesViz(NVD3Viz):
|
||||||
verbose_name = "Time Series - Line Chart"
|
verbose_name = "Time Series - Line Chart"
|
||||||
sort_series = False
|
sort_series = False
|
||||||
is_timeseries = True
|
is_timeseries = True
|
||||||
form_fields = [
|
fieldsets = (
|
||||||
'viz_type',
|
{
|
||||||
'granularity', ('since', 'until'),
|
'label': None,
|
||||||
'metrics',
|
'fields': (
|
||||||
'groupby', 'limit',
|
'viz_type',
|
||||||
('rolling_type', 'rolling_periods'),
|
'granularity', ('since', 'until'),
|
||||||
('time_compare', 'num_period_compare'),
|
'metrics',
|
||||||
('line_interpolation', None),
|
'groupby', 'limit',
|
||||||
('show_brush', 'show_legend'),
|
),
|
||||||
('rich_tooltip', 'y_axis_zero'),
|
}, {
|
||||||
('y_log_scale', 'contribution'),
|
'label': 'Chart Options',
|
||||||
('y_axis_format', 'x_axis_showminmax'),
|
'fields': (
|
||||||
]
|
('show_brush', 'show_legend'),
|
||||||
|
('rich_tooltip', 'y_axis_zero'),
|
||||||
|
('y_log_scale', 'contribution'),
|
||||||
|
('y_axis_format', 'x_axis_showminmax'),
|
||||||
|
('line_interpolation', None),
|
||||||
|
),
|
||||||
|
}, {
|
||||||
|
'label': 'Advanced Analytics',
|
||||||
|
'fields': (
|
||||||
|
('rolling_type', 'rolling_periods'),
|
||||||
|
'time_compare',
|
||||||
|
'num_period_compare',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def get_df(self, query_obj=None):
|
def get_df(self, query_obj=None):
|
||||||
form_data = self.form_data
|
form_data = self.form_data
|
||||||
|
|
Loading…
Reference in New Issue