Introducing field sets

This commit is contained in:
Maxime Beauchemin 2015-12-06 20:57:38 -08:00
parent 6f2c953a9e
commit b666508085
6 changed files with 211 additions and 82 deletions

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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 %}

View File

@ -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):

View File

@ -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