mirror of
https://github.com/apache/superset.git
synced 2024-09-17 11:09:47 -04:00
Adding orderby to Table 'not grouped by' and fixing metrics ordering (#669)
This commit is contained in:
parent
51024b5f8a
commit
131372740e
@ -119,6 +119,7 @@ function tableVis(slice) {
|
|||||||
var height = slice.container.height();
|
var height = slice.container.height();
|
||||||
var datatable = slice.container.find('.dataTable').DataTable({
|
var datatable = slice.container.find('.dataTable').DataTable({
|
||||||
paging: false,
|
paging: false,
|
||||||
|
aaSorting: [],
|
||||||
searching: form_data.include_search,
|
searching: form_data.include_search,
|
||||||
bInfo: false,
|
bInfo: false,
|
||||||
scrollY: height + "px",
|
scrollY: height + "px",
|
||||||
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
import json
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from flask_babelpkg import lazy_gettext as _
|
from flask_babelpkg import lazy_gettext as _
|
||||||
@ -129,6 +130,10 @@ class FormFactory(object):
|
|||||||
gb_cols = datasource.groupby_column_names
|
gb_cols = datasource.groupby_column_names
|
||||||
default_groupby = gb_cols[0] if gb_cols else None
|
default_groupby = gb_cols[0] if gb_cols else None
|
||||||
group_by_choices = self.choicify(gb_cols)
|
group_by_choices = self.choicify(gb_cols)
|
||||||
|
order_by_choices = []
|
||||||
|
for s in sorted(datasource.num_cols):
|
||||||
|
order_by_choices.append((json.dumps([s, True]), s + ' [asc]'))
|
||||||
|
order_by_choices.append((json.dumps([s, False]), s + ' [desc]'))
|
||||||
# Pool of all the fields that can be used in Caravel
|
# Pool of all the fields that can be used in Caravel
|
||||||
field_data = {
|
field_data = {
|
||||||
'viz_type': (SelectField, {
|
'viz_type': (SelectField, {
|
||||||
@ -143,6 +148,11 @@ class FormFactory(object):
|
|||||||
"default": [default_metric],
|
"default": [default_metric],
|
||||||
"description": _("One or many metrics to display")
|
"description": _("One or many metrics to display")
|
||||||
}),
|
}),
|
||||||
|
'order_by_cols': (SelectMultipleSortableField, {
|
||||||
|
"label": _("Ordering"),
|
||||||
|
"choices": order_by_choices,
|
||||||
|
"description": _("One or many metrics to display")
|
||||||
|
}),
|
||||||
'metric': (SelectField, {
|
'metric': (SelectField, {
|
||||||
"label": _("Metric"),
|
"label": _("Metric"),
|
||||||
"choices": datasource.metrics_combo,
|
"choices": datasource.metrics_combo,
|
||||||
|
@ -32,7 +32,7 @@ from pydruid.utils.having import Having, Aggregation
|
|||||||
from six import string_types
|
from six import string_types
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
Column, Integer, String, ForeignKey, Text, Boolean, DateTime, Date,
|
Column, Integer, String, ForeignKey, Text, Boolean, DateTime, Date,
|
||||||
Table, create_engine, MetaData, desc, select, and_, func)
|
Table, create_engine, MetaData, desc, asc, select, and_, func)
|
||||||
from sqlalchemy.engine import reflection
|
from sqlalchemy.engine import reflection
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
@ -533,6 +533,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||||||
'database_id', 'schema', 'table_name',
|
'database_id', 'schema', 'table_name',
|
||||||
name='_customer_location_uc'),)
|
name='_customer_location_uc'),)
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.table_name
|
return self.table_name
|
||||||
|
|
||||||
@ -561,6 +562,10 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||||||
l.append(self.main_dttm_col)
|
l.append(self.main_dttm_col)
|
||||||
return l
|
return l
|
||||||
|
|
||||||
|
@property
|
||||||
|
def num_cols(self):
|
||||||
|
return [c.column_name for c in self.columns if c.isnum]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def any_dttm_col(self):
|
def any_dttm_col(self):
|
||||||
cols = self.dttm_cols
|
cols = self.dttm_cols
|
||||||
@ -610,6 +615,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||||||
is_timeseries=True,
|
is_timeseries=True,
|
||||||
timeseries_limit=15, row_limit=None,
|
timeseries_limit=15, row_limit=None,
|
||||||
inner_from_dttm=None, inner_to_dttm=None,
|
inner_from_dttm=None, inner_to_dttm=None,
|
||||||
|
orderby=None,
|
||||||
extras=None,
|
extras=None,
|
||||||
columns=None):
|
columns=None):
|
||||||
"""Querying any sqla table from this common interface"""
|
"""Querying any sqla table from this common interface"""
|
||||||
@ -618,6 +624,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||||||
granularity = self.main_dttm_col
|
granularity = self.main_dttm_col
|
||||||
|
|
||||||
cols = {col.column_name: col for col in self.columns}
|
cols = {col.column_name: col for col in self.columns}
|
||||||
|
metrics_dict = {m.metric_name: m for m in self.metrics}
|
||||||
qry_start_dttm = datetime.now()
|
qry_start_dttm = datetime.now()
|
||||||
|
|
||||||
if not granularity and is_timeseries:
|
if not granularity and is_timeseries:
|
||||||
@ -625,14 +632,10 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||||||
"Datetime column not provided as part table configuration "
|
"Datetime column not provided as part table configuration "
|
||||||
"and is required by this type of chart"))
|
"and is required by this type of chart"))
|
||||||
|
|
||||||
metrics_exprs = [
|
metrics_exprs = [metrics_dict.get(m).sqla_col for m in metrics]
|
||||||
m.sqla_col
|
|
||||||
for m in self.metrics if m.metric_name in metrics]
|
|
||||||
|
|
||||||
if metrics:
|
if metrics:
|
||||||
main_metric_expr = [
|
main_metric_expr = metrics_exprs[0]
|
||||||
m.sqla_col for m in self.metrics
|
|
||||||
if m.metric_name == metrics[0]][0]
|
|
||||||
else:
|
else:
|
||||||
main_metric_expr = literal_column("COUNT(*)").label("ccount")
|
main_metric_expr = literal_column("COUNT(*)").label("ccount")
|
||||||
|
|
||||||
@ -720,6 +723,11 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||||||
qry = qry.having(and_(*having_clause_and))
|
qry = qry.having(and_(*having_clause_and))
|
||||||
if groupby:
|
if groupby:
|
||||||
qry = qry.order_by(desc(main_metric_expr))
|
qry = qry.order_by(desc(main_metric_expr))
|
||||||
|
elif orderby:
|
||||||
|
for col, ascending in orderby:
|
||||||
|
direction = asc if ascending else desc
|
||||||
|
qry = qry.order_by(direction(col))
|
||||||
|
|
||||||
qry = qry.limit(row_limit)
|
qry = qry.limit(row_limit)
|
||||||
|
|
||||||
if timeseries_limit and groupby:
|
if timeseries_limit and groupby:
|
||||||
@ -768,9 +776,12 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||||||
any_date_col = None
|
any_date_col = None
|
||||||
for col in table.columns:
|
for col in table.columns:
|
||||||
try:
|
try:
|
||||||
datatype = str(col.type)
|
datatype = "{}".format(col.type).upper()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
datatype = "UNKNOWN"
|
datatype = "UNKNOWN"
|
||||||
|
logging.error(
|
||||||
|
"Unrecognized data type in {}.{}".format(table, col.name))
|
||||||
|
logging.exception(e)
|
||||||
dbcol = (
|
dbcol = (
|
||||||
db.session
|
db.session
|
||||||
.query(TC)
|
.query(TC)
|
||||||
@ -780,22 +791,16 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||||||
)
|
)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
if not dbcol:
|
if not dbcol:
|
||||||
dbcol = TableColumn(column_name=col.name)
|
dbcol = TableColumn(column_name=col.name, type=datatype)
|
||||||
num_types = ('DOUBLE', 'FLOAT', 'INT', 'BIGINT', 'LONG')
|
dbcol.groupby = dbcol.is_string
|
||||||
date_types = ('DATE', 'TIME')
|
dbcol.filterable = dbcol.is_string
|
||||||
str_types = ('VARCHAR', 'STRING')
|
dbcol.sum = dbcol.isnum
|
||||||
datatype = str(datatype).upper()
|
dbcol.is_dttm = dbcol.is_time
|
||||||
if any([t in datatype for t in str_types]):
|
|
||||||
dbcol.groupby = True
|
|
||||||
dbcol.filterable = True
|
|
||||||
elif any([t in datatype for t in num_types]):
|
|
||||||
dbcol.sum = True
|
|
||||||
elif any([t in datatype for t in date_types]):
|
|
||||||
dbcol.is_dttm = True
|
|
||||||
db.session.merge(self)
|
db.session.merge(self)
|
||||||
self.columns.append(dbcol)
|
self.columns.append(dbcol)
|
||||||
|
|
||||||
if not any_date_col and 'date' in datatype.lower():
|
if not any_date_col and dbcol.is_time:
|
||||||
any_date_col = col.name
|
any_date_col = col.name
|
||||||
|
|
||||||
quoted = "{}".format(
|
quoted = "{}".format(
|
||||||
@ -905,13 +910,24 @@ class TableColumn(Model, AuditMixinNullable):
|
|||||||
expression = Column(Text, default='')
|
expression = Column(Text, default='')
|
||||||
description = Column(Text, default='')
|
description = Column(Text, default='')
|
||||||
|
|
||||||
|
num_types = ('DOUBLE', 'FLOAT', 'INT', 'BIGINT', 'LONG')
|
||||||
|
date_types = ('DATE', 'TIME')
|
||||||
|
str_types = ('VARCHAR', 'STRING', 'CHAR')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.column_name
|
return self.column_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def isnum(self):
|
def isnum(self):
|
||||||
types = ('LONG', 'DOUBLE', 'FLOAT', 'BIGINT', 'INT')
|
return any([t in self.type.upper() for t in self.num_types])
|
||||||
return any([t in self.type.upper() for t in types])
|
|
||||||
|
@property
|
||||||
|
def is_time(self):
|
||||||
|
return any([t in self.type.upper() for t in self.date_types])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_string(self):
|
||||||
|
return any([t in self.type.upper() for t in self.str_types])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sqla_col(self):
|
def sqla_col(self):
|
||||||
@ -999,6 +1015,10 @@ class DruidDatasource(Model, AuditMixinNullable, Queryable):
|
|||||||
[(m.metric_name, m.verbose_name) for m in self.metrics],
|
[(m.metric_name, m.verbose_name) for m in self.metrics],
|
||||||
key=lambda x: x[1])
|
key=lambda x: x[1])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def num_cols(self):
|
||||||
|
return [c.column_name for c in self.columns if c.isnum]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return self.datasource_name
|
return self.datasource_name
|
||||||
@ -1119,6 +1139,7 @@ class DruidDatasource(Model, AuditMixinNullable, Queryable):
|
|||||||
timeseries_limit=None,
|
timeseries_limit=None,
|
||||||
row_limit=None,
|
row_limit=None,
|
||||||
inner_from_dttm=None, inner_to_dttm=None,
|
inner_from_dttm=None, inner_to_dttm=None,
|
||||||
|
orderby=None,
|
||||||
extras=None, # noqa
|
extras=None, # noqa
|
||||||
select=None,): # noqa
|
select=None,): # noqa
|
||||||
"""Runs a query against Druid and returns a dataframe.
|
"""Runs a query against Druid and returns a dataframe.
|
||||||
@ -1458,7 +1479,7 @@ class DruidColumn(Model, AuditMixinNullable):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def isnum(self):
|
def isnum(self):
|
||||||
return self.type in ('LONG', 'DOUBLE', 'FLOAT')
|
return self.type in ('LONG', 'DOUBLE', 'FLOAT', 'INT')
|
||||||
|
|
||||||
def generate_metrics(self):
|
def generate_metrics(self):
|
||||||
"""Generate metrics based on the column metadata"""
|
"""Generate metrics based on the column metadata"""
|
||||||
|
@ -350,16 +350,11 @@ class TableViz(BaseViz):
|
|||||||
fieldsets = ({
|
fieldsets = ({
|
||||||
'label': _("GROUP BY"),
|
'label': _("GROUP BY"),
|
||||||
'description': _('Use this section if you want a query that aggregates'),
|
'description': _('Use this section if you want a query that aggregates'),
|
||||||
'fields': (
|
'fields': ('groupby', 'metrics')
|
||||||
'groupby',
|
|
||||||
'metrics',
|
|
||||||
)
|
|
||||||
}, {
|
}, {
|
||||||
'label': _("NOT GROUPED BY"),
|
'label': _("NOT GROUPED BY"),
|
||||||
'description': _('Use this section if you want to query atomic rows'),
|
'description': _('Use this section if you want to query atomic rows'),
|
||||||
'fields': (
|
'fields': ('all_columns', 'order_by_cols'),
|
||||||
'all_columns',
|
|
||||||
)
|
|
||||||
}, {
|
}, {
|
||||||
'label': _("Options"),
|
'label': _("Options"),
|
||||||
'fields': (
|
'fields': (
|
||||||
@ -385,6 +380,7 @@ class TableViz(BaseViz):
|
|||||||
if fd.get('all_columns'):
|
if fd.get('all_columns'):
|
||||||
d['columns'] = fd.get('all_columns')
|
d['columns'] = fd.get('all_columns')
|
||||||
d['groupby'] = []
|
d['groupby'] = []
|
||||||
|
d['orderby'] = [json.loads(t) for t in fd.get('order_by_cols', [])]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def get_df(self, query_obj=None):
|
def get_df(self, query_obj=None):
|
||||||
|
Loading…
Reference in New Issue
Block a user