diff --git a/TODO.md b/TODO.md index 200b7ab09ce7f..a89dbace15e34 100644 --- a/TODO.md +++ b/TODO.md @@ -2,13 +2,10 @@ List of TODO items for Panoramix ## Improvments -* GROUPED boolean for table view * datasource in explore mode could be a dropdown * [sql] make "Test Connection" test further -* in/notin filters autocomplete * [druid] Allow for post aggregations (ratios!) * in/notin filters autocomplete -* [sql] make "Test Connection" test further ## Better Javascript enables * Async on Druidify! in exploration page diff --git a/panoramix/forms.py b/panoramix/forms.py index 3213d3e4bb2f9..9ebb0cd5236e9 100644 --- a/panoramix/forms.py +++ b/panoramix/forms.py @@ -128,6 +128,10 @@ def __init__(self, viz): 'Columns', choices=self.choicify(datasource.groupby_column_names), description="One or many fields to pivot as columns"), + 'all_columns': SelectMultipleSortableField( + 'Columns', + choices=self.choicify(datasource.column_names), + description="Columns to display"), 'granularity': FreeFormSelectField( 'Time Granularity', default="one day", choices=self.choicify([ @@ -322,6 +326,10 @@ def __init__(self, viz): "Range Filter", default=True, description=( "Whether to display the time range interactive selector")), + 'include_search': BetterBooleanField( + "Search Box", default=False, + description=( + "Whether to include a client side search box")), 'show_bubbles': BetterBooleanField( "Show Bubbles", default=False, description=( @@ -335,10 +343,14 @@ def __init__(self, viz): "Whether to display the min and max values of the axis")), 'rich_tooltip': BetterBooleanField( "Rich Tooltip", default=True, - description="The rich tooltip shows a list of all series for that point in time"), + description=( + "The rich tooltip shows a list of all series for that" + " point in time")), 'y_axis_zero': BetterBooleanField( "Y Axis Zero", default=False, - description="Force the Y axis to start at 0 instead of the minimum value"), + description=( + "Force the Y axis to start at 0 instead of the minimum " + "value")), 'y_log_scale': BetterBooleanField( "Y Log", default=False, description="Use a log scale for the Y axis"), diff --git a/panoramix/models.py b/panoramix/models.py index a2357b27a95a6..a8eb617f260a1 100644 --- a/panoramix/models.py +++ b/panoramix/models.py @@ -377,7 +377,8 @@ def query( is_timeseries=True, timeseries_limit=15, row_limit=None, inner_from_dttm=None, inner_to_dttm=None, - extras=None): + extras=None, + columns=None): # For backward compatibility if granularity not in self.dttm_cols: @@ -427,15 +428,20 @@ def query( select_exprs.append(outer) inner_groupby_exprs.append(inner) inner_select_exprs.append(inner) + elif columns: + for s in columns: + select_exprs.append(s) + metrics_exprs = [] - if is_timeseries: + if is_timeseries and groupby: select_exprs += [timestamp] groupby_exprs += [timestamp] select_exprs += metrics_exprs qry = select(select_exprs) from_clause = table(self.table_name) - qry = qry.group_by(*groupby_exprs) + if groupby: + qry = qry.group_by(*groupby_exprs) time_filter = [ timestamp >= from_dttm.isoformat(), @@ -466,7 +472,8 @@ def query( having_clause_and += [text(extras['having'])] qry = qry.where(and_(*(time_filter + where_clause_and))) qry = qry.having(and_(*having_clause_and)) - qry = qry.order_by(desc(main_metric_expr)) + if groupby: + qry = qry.order_by(desc(main_metric_expr)) qry = qry.limit(row_limit) if timeseries_limit and groupby: @@ -492,6 +499,7 @@ def query( con=engine ) sql = sqlparse.format(sql, reindent=True) + print(sql) return QueryResult( df=df, duration=datetime.now() - qry_start_dttm, query=sql) @@ -779,7 +787,8 @@ def query( timeseries_limit=None, row_limit=None, inner_from_dttm=None, inner_to_dttm=None, - extras=None): + extras=None, + select=None): qry_start_dttm = datetime.now() inner_from_dttm = inner_from_dttm or from_dttm diff --git a/panoramix/static/panoramix.css b/panoramix/static/panoramix.css index 1ea5b1c2833a0..7c2ce689e5b2d 100644 --- a/panoramix/static/panoramix.css +++ b/panoramix/static/panoramix.css @@ -1,3 +1,9 @@ +html>body{ + margin: 0px; !important +} +.container-fluid { + text-align: left; +} input[type="checkbox"] { display: inline-block; width: 16px; diff --git a/panoramix/static/widgets/viz_pivot_table.js b/panoramix/static/widgets/viz_pivot_table.js index dfd5649a6b616..42382daad0910 100644 --- a/panoramix/static/widgets/viz_pivot_table.js +++ b/panoramix/static/widgets/viz_pivot_table.js @@ -4,26 +4,24 @@ px.registerWidget('pivot_table', function(data_attribute) { var form_data = data_attribute.form_data; function refresh(done) { - token.load(data_attribute.json_endpoint, function(response, status, xhr){ - if(status=="error"){ + $.getJSON(data_attribute.json_endpoint, function(json){ + token.html(json.data); + if (form_data.groupby.length == 1){ + var table = token.find('table').DataTable({ + paging: false, + searching: false, + }); + table.column('-1').order( 'desc' ).draw(); + } + token.show(); + done(json); + }).fail(function(xhr){ var err = '
' + xhr.responseText + '
'; token.html(err); token.show(); - } - else{ - if (form_data.groupby.length == 1){ - var table = token.find('table').DataTable({ - paging: false, - searching: false, - }); - table.column('-1').order( 'desc' ).draw(); - } - } - token.show(); - done(); + done(); }); } - return { render: refresh, resize: refresh, diff --git a/panoramix/static/widgets/viz_table.js b/panoramix/static/widgets/viz_table.js index 2272481b5e140..5f628a28d2fd0 100644 --- a/panoramix/static/widgets/viz_table.js +++ b/panoramix/static/widgets/viz_table.js @@ -4,20 +4,56 @@ px.registerWidget('table', function(data_attribute) { var token = $('#' + token_name); function refresh(done) { - token.load(data_attribute.json_endpoint, function(response, status, xhr){ - if(status=="error"){ - var err = '
' + xhr.responseText + '
'; - token.html(err); - token.show(); - done(); + $.getJSON(data_attribute.json_endpoint, function(json){ + var data = json.data; + var metrics = json.form_data.metrics; + function col(c){ + arr = []; + for (var i=0; i 0) { + var main_metric = data_attribute.form_data.metrics[0]; + datatable.column(data.columns.indexOf(main_metric)).order( 'desc' ).draw(); + } + done(json); + }).fail(function(xhr){ + var err = '
' + xhr.responseText + '
'; + token.html(err); token.show(); done(); }); @@ -25,7 +61,6 @@ px.registerWidget('table', function(data_attribute) { return { render: refresh, - resize: refresh, + resize: function(){}, }; - }); diff --git a/panoramix/templates/panoramix/viz_pivot_table.html b/panoramix/templates/panoramix/viz_pivot_table.html index 9fe1398ea1c53..062c23bfd5530 100644 --- a/panoramix/templates/panoramix/viz_pivot_table.html +++ b/panoramix/templates/panoramix/viz_pivot_table.html @@ -1,18 +1,7 @@ {% macro viz_html(viz) %} - {% if viz.request.args.get("async") == "true" %} - {{ viz.get_df().to_html(na_rep='', classes="dataframe table table-striped table-bordered table-condensed")|safe }} - {% else %} - - {% endif %} {% endmacro %} {% macro viz_js(viz) %} - {% if viz.form_data.get("async") != "true" %} - - {% endif %} {% endmacro %} {% macro viz_css(viz) %} diff --git a/panoramix/templates/panoramix/viz_table.html b/panoramix/templates/panoramix/viz_table.html index ef26a192fa900..9497ff9128ca0 100644 --- a/panoramix/templates/panoramix/viz_table.html +++ b/panoramix/templates/panoramix/viz_table.html @@ -2,7 +2,7 @@ {% if viz.request.args.get("async") == "true" %} {% set df = viz.get_df() %}
- +
{% for col in df.columns if not col.endswith('__perc') %} diff --git a/panoramix/viz.py b/panoramix/viz.py index 3f42103bdaec9..f332cddf3bb08 100644 --- a/panoramix/viz.py +++ b/panoramix/viz.py @@ -242,25 +242,43 @@ class TableViz(BaseViz): 'fields': ( 'granularity', ('since', 'until'), - 'metrics', 'groupby', - 'row_limit' + 'row_limit', + ('include_search', None), + ) + }, + { + 'label': "GROUP BY", + 'fields': ( + 'groupby', + 'metrics', + ) + }, + { + 'label': "NOT GROUPED BY", + 'fields': ( + 'all_columns', ) },) css_files = ['lib/dataTables/dataTables.bootstrap.css'] is_timeseries = False js_files = [ + 'lib/d3.min.js', 'lib/dataTables/jquery.dataTables.min.js', 'lib/dataTables/dataTables.bootstrap.js', 'widgets/viz_table.js', ] css_files = ['widgets/viz_table.css'] - @property - def json_endpoint(self): - return self.get_url(async='true', standalone='true', skip_libs='true') - def query_obj(self): d = super(TableViz, self).query_obj() + fd = self.form_data + if fd.get('all_columns') and (fd.get('groupby') or fd.get('metrics')): + raise Exception( + "Choose either fields to [Group By] and [Metrics] or " + "[Columns], not both") + if fd.get('all_columns'): + d['columns'] = fd.get('all_columns') + d['groupby'] = [] d['is_timeseries'] = False d['timeseries_limit'] = None return d @@ -271,10 +289,15 @@ def get_df(self): self.form_data.get("granularity") == "all" and 'timestamp' in df): del df['timestamp'] - for m in self.metrics: - df[m + '__perc'] = np.rint((df[m] / np.max(df[m])) * 100) return df + def get_json_data(self): + df = self.get_df() + return dumps(dict( + records=df.to_dict(orient="records"), + columns=df.columns, + )) + class PivotTableViz(BaseViz): viz_type = "pivot_table" @@ -301,10 +324,6 @@ class PivotTableViz(BaseViz): ) },) - @property - def json_endpoint(self): - return self.get_url(async='true', standalone='true', skip_libs='true') - def query_obj(self): d = super(PivotTableViz, self).query_obj() groupby = self.form_data.get('groupby') @@ -343,6 +362,13 @@ def get_df(self): ) return df + def get_json_data(self): + return dumps(self.get_df().to_html( + na_rep='', + classes=( + "dataframe table table-striped table-bordered " + "table-condensed table-hover"))) + class MarkupViz(BaseViz): viz_type = "markup"