2015-07-30 02:39:30 -04:00
|
|
|
from datetime import datetime
|
2015-07-20 19:29:16 -04:00
|
|
|
import json
|
2015-09-05 12:23:46 -04:00
|
|
|
import logging
|
2015-07-16 20:55:36 -04:00
|
|
|
|
2015-07-20 19:29:16 -04:00
|
|
|
from flask import request, redirect, flash, Response
|
2015-07-15 13:12:32 -04:00
|
|
|
from flask.ext.appbuilder import ModelView, CompactCRUDMixin, BaseView, expose
|
2015-09-24 00:21:48 -04:00
|
|
|
from flask.ext.appbuilder.actions import action
|
|
|
|
from flask.ext.appbuilder.models.sqla.interface import SQLAInterface
|
2015-09-05 12:23:46 -04:00
|
|
|
from flask.ext.appbuilder.security.decorators import has_access
|
2015-07-27 01:25:32 -04:00
|
|
|
from pydruid.client import doublesum
|
2015-09-24 00:21:48 -04:00
|
|
|
from sqlalchemy import create_engine
|
2015-07-28 19:06:06 -04:00
|
|
|
from wtforms.validators import ValidationError
|
|
|
|
|
2015-09-25 18:43:50 -04:00
|
|
|
from panoramix import appbuilder, db, models, viz, utils, app
|
|
|
|
|
|
|
|
config = app.config
|
2015-07-28 19:06:06 -04:00
|
|
|
|
2015-09-09 13:37:59 -04:00
|
|
|
|
2015-07-28 19:06:06 -04:00
|
|
|
def validate_json(form, field):
|
|
|
|
try:
|
|
|
|
json.loads(field.data)
|
|
|
|
except Exception as e:
|
2015-09-09 13:37:59 -04:00
|
|
|
logging.exception(e)
|
2015-07-28 19:06:06 -04:00
|
|
|
raise ValidationError("Json isn't valid")
|
2015-07-15 13:12:32 -04:00
|
|
|
|
2015-09-09 13:37:59 -04:00
|
|
|
|
2015-07-29 17:48:57 -04:00
|
|
|
class DeleteMixin(object):
|
2015-09-09 13:37:59 -04:00
|
|
|
@action(
|
|
|
|
"muldelete", "Delete", "Delete all Really?", "fa-trash", single=False)
|
2015-07-29 17:48:57 -04:00
|
|
|
def muldelete(self, items):
|
|
|
|
self.datamodel.delete_all(items)
|
|
|
|
self.update_redirect()
|
|
|
|
return redirect(self.get_redirect())
|
|
|
|
|
2015-07-17 02:46:00 -04:00
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class PanoramixModelView(ModelView):
|
|
|
|
page_size = 100
|
|
|
|
|
|
|
|
|
|
|
|
class TableColumnInlineView(CompactCRUDMixin, PanoramixModelView):
|
2015-08-03 11:34:58 -04:00
|
|
|
datamodel = SQLAInterface(models.TableColumn)
|
|
|
|
can_delete = False
|
|
|
|
edit_columns = [
|
2015-09-08 17:56:27 -04:00
|
|
|
'column_name', 'description', 'groupby', 'filterable', 'table',
|
2015-08-03 11:34:58 -04:00
|
|
|
'count_distinct', 'sum', 'min', 'max']
|
|
|
|
list_columns = [
|
2015-08-11 00:12:21 -04:00
|
|
|
'column_name', 'type', 'groupby', 'filterable', 'count_distinct',
|
2015-08-03 11:34:58 -04:00
|
|
|
'sum', 'min', 'max']
|
|
|
|
page_size = 100
|
|
|
|
appbuilder.add_view_no_menu(TableColumnInlineView)
|
|
|
|
|
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class ColumnInlineView(CompactCRUDMixin, PanoramixModelView):
|
2015-07-15 13:12:32 -04:00
|
|
|
datamodel = SQLAInterface(models.Column)
|
2015-07-15 20:38:03 -04:00
|
|
|
edit_columns = [
|
2015-07-21 14:56:05 -04:00
|
|
|
'column_name', 'description', 'datasource', 'groupby',
|
|
|
|
'count_distinct', 'sum', 'min', 'max']
|
2015-07-15 20:38:03 -04:00
|
|
|
list_columns = [
|
2015-08-11 00:12:21 -04:00
|
|
|
'column_name', 'type', 'groupby', 'filterable', 'count_distinct',
|
2015-07-15 20:38:03 -04:00
|
|
|
'sum', 'min', 'max']
|
2015-07-15 13:12:32 -04:00
|
|
|
can_delete = False
|
2015-07-22 19:21:46 -04:00
|
|
|
page_size = 100
|
2015-07-28 19:06:06 -04:00
|
|
|
|
|
|
|
def post_update(self, col):
|
|
|
|
col.generate_metrics()
|
|
|
|
|
2015-07-15 13:12:32 -04:00
|
|
|
appbuilder.add_view_no_menu(ColumnInlineView)
|
|
|
|
|
2015-09-09 13:37:59 -04:00
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class SqlMetricInlineView(CompactCRUDMixin, PanoramixModelView):
|
2015-08-05 02:41:00 -04:00
|
|
|
datamodel = SQLAInterface(models.SqlMetric)
|
2015-09-09 13:37:59 -04:00
|
|
|
list_columns = ['metric_name', 'verbose_name', 'metric_type']
|
2015-08-05 02:41:00 -04:00
|
|
|
edit_columns = [
|
|
|
|
'metric_name', 'description', 'verbose_name', 'metric_type',
|
2015-09-09 13:37:59 -04:00
|
|
|
'expression', 'table']
|
2015-08-05 02:41:00 -04:00
|
|
|
add_columns = edit_columns
|
|
|
|
page_size = 100
|
|
|
|
appbuilder.add_view_no_menu(SqlMetricInlineView)
|
|
|
|
|
2015-07-15 20:38:03 -04:00
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class MetricInlineView(CompactCRUDMixin, PanoramixModelView):
|
2015-07-15 20:38:03 -04:00
|
|
|
datamodel = SQLAInterface(models.Metric)
|
2015-09-09 13:37:59 -04:00
|
|
|
list_columns = ['metric_name', 'verbose_name', 'metric_type']
|
2015-07-15 20:38:03 -04:00
|
|
|
edit_columns = [
|
2015-07-21 14:56:05 -04:00
|
|
|
'metric_name', 'description', 'verbose_name', 'metric_type',
|
|
|
|
'datasource', 'json']
|
2015-07-15 20:38:03 -04:00
|
|
|
add_columns = [
|
|
|
|
'metric_name', 'verbose_name', 'metric_type', 'datasource', 'json']
|
2015-07-22 19:21:46 -04:00
|
|
|
page_size = 100
|
2015-07-28 19:06:06 -04:00
|
|
|
validators_columns = {
|
|
|
|
'json': [validate_json],
|
|
|
|
}
|
2015-07-15 20:38:03 -04:00
|
|
|
appbuilder.add_view_no_menu(MetricInlineView)
|
2015-07-15 13:12:32 -04:00
|
|
|
|
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class DatabaseView(PanoramixModelView, DeleteMixin):
|
2015-09-19 02:48:41 -04:00
|
|
|
datamodel = SQLAInterface(models.Database)
|
2015-09-24 01:55:21 -04:00
|
|
|
list_columns = ['database_name', 'created_by', 'created_on']
|
2015-09-19 02:48:41 -04:00
|
|
|
add_columns = ['database_name', 'sqlalchemy_uri']
|
|
|
|
edit_columns = add_columns
|
2015-09-24 00:21:48 -04:00
|
|
|
add_template = "panoramix/models/database/add.html"
|
|
|
|
edit_template = "panoramix/models/database/edit.html"
|
2015-09-24 00:28:01 -04:00
|
|
|
description_columns = {
|
|
|
|
'sqlalchemy_uri': (
|
|
|
|
"Refer to the SqlAlchemy docs for more information on how "
|
|
|
|
"to structure your URI here: "
|
|
|
|
"http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html")
|
|
|
|
}
|
2015-09-19 02:48:41 -04:00
|
|
|
|
|
|
|
appbuilder.add_view(
|
|
|
|
DatabaseView,
|
|
|
|
"Databases",
|
|
|
|
icon="fa-database",
|
|
|
|
category="Sources",
|
2015-09-21 00:56:23 -04:00
|
|
|
category_icon='fa-database',)
|
2015-09-19 02:48:41 -04:00
|
|
|
|
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class TableView(PanoramixModelView, DeleteMixin):
|
2015-09-19 02:48:41 -04:00
|
|
|
datamodel = SQLAInterface(models.Table)
|
|
|
|
list_columns = ['table_link', 'database']
|
|
|
|
add_columns = ['table_name', 'database', 'default_endpoint']
|
|
|
|
edit_columns = [
|
|
|
|
'table_name', 'database', 'main_dttm_col', 'default_endpoint']
|
|
|
|
related_views = [TableColumnInlineView, SqlMetricInlineView]
|
|
|
|
|
|
|
|
def post_add(self, table):
|
|
|
|
table.fetch_metadata()
|
|
|
|
|
|
|
|
def post_update(self, table):
|
|
|
|
table.fetch_metadata()
|
|
|
|
|
|
|
|
appbuilder.add_view(
|
|
|
|
TableView,
|
|
|
|
"Tables",
|
|
|
|
category="Sources",
|
|
|
|
icon='fa-table',)
|
|
|
|
|
|
|
|
|
|
|
|
appbuilder.add_separator("Sources")
|
|
|
|
|
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class ClusterModelView(PanoramixModelView, DeleteMixin):
|
2015-07-30 02:39:30 -04:00
|
|
|
datamodel = SQLAInterface(models.Cluster)
|
|
|
|
add_columns = [
|
|
|
|
'cluster_name',
|
|
|
|
'coordinator_host', 'coordinator_port', 'coordinator_endpoint',
|
|
|
|
'broker_host', 'broker_port', 'broker_endpoint',
|
|
|
|
]
|
|
|
|
edit_columns = add_columns
|
|
|
|
list_columns = ['cluster_name', 'metadata_last_refreshed']
|
|
|
|
|
|
|
|
appbuilder.add_view(
|
|
|
|
ClusterModelView,
|
2015-08-01 20:16:39 -04:00
|
|
|
"Druid Clusters",
|
2015-09-19 16:52:28 -04:00
|
|
|
icon="fa-cubes",
|
2015-09-19 02:48:41 -04:00
|
|
|
category="Sources",
|
2015-09-21 00:56:23 -04:00
|
|
|
category_icon='fa-database',)
|
2015-07-30 02:39:30 -04:00
|
|
|
|
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class SliceModelView(PanoramixModelView, DeleteMixin):
|
2015-09-12 00:49:04 -04:00
|
|
|
datamodel = SQLAInterface(models.Slice)
|
2015-09-21 16:48:02 -04:00
|
|
|
can_add = False
|
2015-09-21 18:49:30 -04:00
|
|
|
list_columns = [
|
|
|
|
'slice_link', 'viz_type', 'datasource_type',
|
|
|
|
'datasource', 'created_by']
|
2015-09-19 02:48:41 -04:00
|
|
|
edit_columns = [
|
2015-09-21 18:49:30 -04:00
|
|
|
'slice_name', 'viz_type', 'druid_datasource',
|
|
|
|
'table', 'dashboards', 'params']
|
2015-09-12 00:49:04 -04:00
|
|
|
|
|
|
|
appbuilder.add_view(
|
|
|
|
SliceModelView,
|
|
|
|
"Slices",
|
|
|
|
icon="fa-bar-chart",
|
|
|
|
category="",
|
|
|
|
category_icon='',)
|
|
|
|
|
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class DashboardModelView(PanoramixModelView, DeleteMixin):
|
2015-09-13 22:07:54 -04:00
|
|
|
datamodel = SQLAInterface(models.Dashboard)
|
|
|
|
list_columns = ['dashboard_link', 'created_by']
|
2015-09-17 21:06:03 -04:00
|
|
|
edit_columns = ['dashboard_title', 'slices', 'position_json']
|
2015-09-13 22:07:54 -04:00
|
|
|
add_columns = edit_columns
|
|
|
|
|
|
|
|
|
|
|
|
appbuilder.add_view(
|
|
|
|
DashboardModelView,
|
|
|
|
"Dashboards",
|
|
|
|
icon="fa-dashboard",
|
|
|
|
category="",
|
|
|
|
category_icon='',)
|
|
|
|
|
|
|
|
|
2015-09-25 19:15:13 -04:00
|
|
|
class DatasourceModelView(PanoramixModelView, DeleteMixin):
|
2015-07-15 13:12:32 -04:00
|
|
|
datamodel = SQLAInterface(models.Datasource)
|
2015-07-30 02:39:30 -04:00
|
|
|
list_columns = [
|
|
|
|
'datasource_link', 'cluster', 'owner', 'is_featured', 'is_hidden']
|
2015-07-15 20:38:03 -04:00
|
|
|
related_views = [ColumnInlineView, MetricInlineView]
|
|
|
|
edit_columns = [
|
2015-07-30 02:39:30 -04:00
|
|
|
'datasource_name', 'cluster', 'description', 'owner',
|
|
|
|
'is_featured', 'is_hidden', 'default_endpoint']
|
2015-07-15 13:12:32 -04:00
|
|
|
page_size = 100
|
2015-07-27 01:25:32 -04:00
|
|
|
base_order = ('datasource_name', 'asc')
|
2015-07-15 13:12:32 -04:00
|
|
|
|
2015-08-23 17:57:08 -04:00
|
|
|
def post_add(self, datasource):
|
2015-07-28 19:06:06 -04:00
|
|
|
datasource.generate_metrics()
|
|
|
|
|
|
|
|
def post_update(self, datasource):
|
|
|
|
datasource.generate_metrics()
|
|
|
|
|
2015-07-15 13:12:32 -04:00
|
|
|
|
|
|
|
appbuilder.add_view(
|
|
|
|
DatasourceModelView,
|
2015-08-01 20:16:39 -04:00
|
|
|
"Druid Datasources",
|
2015-09-19 02:48:41 -04:00
|
|
|
category="Sources",
|
2015-09-19 16:52:28 -04:00
|
|
|
icon="fa-cube")
|
2015-07-15 13:12:32 -04:00
|
|
|
|
|
|
|
|
2015-07-27 17:16:18 -04:00
|
|
|
@app.route('/health')
|
|
|
|
def health():
|
|
|
|
return "OK"
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/ping')
|
|
|
|
def ping():
|
|
|
|
return "OK"
|
|
|
|
|
|
|
|
|
2015-07-15 13:12:32 -04:00
|
|
|
class Panoramix(BaseView):
|
2015-08-03 11:34:58 -04:00
|
|
|
@has_access
|
2015-09-18 18:29:49 -04:00
|
|
|
@expose("/datasource/<datasource_type>/<datasource_id>/")
|
|
|
|
def datasource(self, datasource_type, datasource_id):
|
2015-09-21 16:48:02 -04:00
|
|
|
action = request.args.get('action')
|
|
|
|
if action == 'save':
|
|
|
|
session = db.session()
|
|
|
|
d = request.args.to_dict(flat=False)
|
|
|
|
del d['action']
|
|
|
|
as_list = ('metrics', 'groupby')
|
|
|
|
for k in d:
|
|
|
|
v = d.get(k)
|
|
|
|
if k in as_list and not isinstance(v, list):
|
|
|
|
d[k] = [v] if v else []
|
|
|
|
if k not in as_list and isinstance(v, list):
|
|
|
|
d[k] = v[0]
|
|
|
|
|
|
|
|
table_id = druid_datasource_id = None
|
|
|
|
datasource_type = request.args.get('datasource_type')
|
2015-09-21 18:49:30 -04:00
|
|
|
if datasource_type in ('datasource', 'druid'):
|
2015-09-21 16:48:02 -04:00
|
|
|
druid_datasource_id = request.args.get('datasource_id')
|
2015-09-21 18:49:30 -04:00
|
|
|
elif datasource_type == 'table':
|
2015-09-21 16:48:02 -04:00
|
|
|
table_id = request.args.get('datasource_id')
|
2015-09-21 18:49:30 -04:00
|
|
|
|
2015-09-21 16:48:02 -04:00
|
|
|
slice_name = request.args.get('slice_name')
|
|
|
|
|
|
|
|
obj = models.Slice(
|
|
|
|
params=json.dumps(d, indent=4, sort_keys=True),
|
|
|
|
viz_type=request.args.get('viz_type'),
|
|
|
|
datasource_name=request.args.get('datasource_name'),
|
|
|
|
druid_datasource_id=druid_datasource_id,
|
|
|
|
table_id=table_id,
|
|
|
|
datasource_type=datasource_type,
|
|
|
|
slice_name=slice_name,
|
|
|
|
)
|
|
|
|
session.add(obj)
|
|
|
|
session.commit()
|
|
|
|
flash("Slice <{}> has been added to the pie".format(slice_name), "info")
|
|
|
|
redirect(obj.slice_url)
|
|
|
|
|
2015-09-18 18:29:49 -04:00
|
|
|
if datasource_type == "table":
|
|
|
|
datasource = (
|
|
|
|
db.session
|
|
|
|
.query(models.Table)
|
|
|
|
.filter_by(id=datasource_id)
|
|
|
|
.first()
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
datasource = (
|
|
|
|
db.session
|
|
|
|
.query(models.Datasource)
|
|
|
|
.filter_by(id=datasource_id)
|
|
|
|
.first()
|
|
|
|
)
|
|
|
|
|
|
|
|
if not datasource:
|
|
|
|
flash("The datasource seem to have been deleted", "alert")
|
2015-08-03 11:34:58 -04:00
|
|
|
viz_type = request.args.get("viz_type")
|
2015-09-18 18:29:49 -04:00
|
|
|
if not viz_type and datasource.default_endpoint:
|
|
|
|
return redirect(datasource.default_endpoint)
|
2015-08-03 11:34:58 -04:00
|
|
|
if not viz_type:
|
|
|
|
viz_type = "table"
|
|
|
|
obj = viz.viz_types[viz_type](
|
2015-09-18 18:29:49 -04:00
|
|
|
datasource,
|
2015-09-17 21:06:03 -04:00
|
|
|
form_data=request.args)
|
2015-09-15 12:17:59 -04:00
|
|
|
if request.args.get("json") == "true":
|
2015-09-27 11:52:26 -04:00
|
|
|
if config.get("DEBUG"):
|
|
|
|
payload = obj.get_json()
|
2015-09-15 15:33:26 -04:00
|
|
|
try:
|
|
|
|
payload = obj.get_json()
|
|
|
|
status=200
|
|
|
|
except Exception as e:
|
2015-09-17 21:06:03 -04:00
|
|
|
logging.exception(e)
|
2015-09-15 15:33:26 -04:00
|
|
|
payload = str(e)
|
|
|
|
status=500
|
2015-08-03 11:34:58 -04:00
|
|
|
return Response(
|
2015-09-15 15:33:26 -04:00
|
|
|
payload,
|
|
|
|
status=status,
|
2015-08-03 11:34:58 -04:00
|
|
|
mimetype="application/json")
|
2015-09-15 12:17:59 -04:00
|
|
|
else:
|
2015-09-27 11:52:26 -04:00
|
|
|
if config.get("DEBUG"):
|
|
|
|
resp = self.render_template("panoramix/viz.html", viz=obj)
|
2015-09-22 19:53:06 -04:00
|
|
|
try:
|
|
|
|
resp = self.render_template("panoramix/viz.html", viz=obj)
|
2015-09-19 02:48:41 -04:00
|
|
|
except Exception as e:
|
2015-09-25 18:43:50 -04:00
|
|
|
if config.get("DEBUG"):
|
2015-09-22 19:34:11 -04:00
|
|
|
raise(e)
|
2015-09-19 02:48:41 -04:00
|
|
|
return Response(
|
|
|
|
str(e),
|
|
|
|
status=500,
|
|
|
|
mimetype="application/json")
|
|
|
|
return resp
|
2015-08-03 11:34:58 -04:00
|
|
|
|
2015-09-17 21:06:03 -04:00
|
|
|
@has_access
|
|
|
|
@expose("/save_dash/<dashboard_id>/", methods=['GET', 'POST'])
|
|
|
|
def save_dash(self, dashboard_id):
|
|
|
|
data = json.loads(request.form.get('data'))
|
|
|
|
slice_ids = [int(d['slice_id']) for d in data]
|
|
|
|
print slice_ids
|
|
|
|
session = db.session()
|
|
|
|
Dash = models.Dashboard
|
|
|
|
dash = session.query(Dash).filter_by(id=dashboard_id).first()
|
|
|
|
dash.slices = [o for o in dash.slices if o.id in slice_ids]
|
|
|
|
print dash.slices
|
|
|
|
dash.position_json = json.dumps(data, indent=4)
|
|
|
|
session.merge(dash)
|
|
|
|
session.commit()
|
|
|
|
session.close()
|
|
|
|
return "SUCCESS"
|
|
|
|
|
2015-09-12 00:49:04 -04:00
|
|
|
@has_access
|
2015-09-25 18:43:50 -04:00
|
|
|
@expose("/testconn", methods=["POST"])
|
2015-09-24 00:21:48 -04:00
|
|
|
def testconn(self):
|
|
|
|
try:
|
2015-09-25 18:43:50 -04:00
|
|
|
uri = request.form.get('uri')
|
|
|
|
db = create_engine(uri)
|
2015-09-24 00:21:48 -04:00
|
|
|
db.connect()
|
|
|
|
return "SUCCESS"
|
|
|
|
except Exception as e:
|
|
|
|
return Response(
|
|
|
|
str(e),
|
|
|
|
status=500,
|
|
|
|
mimetype="application/json")
|
2015-09-12 00:49:04 -04:00
|
|
|
|
2015-09-13 02:25:43 -04:00
|
|
|
@has_access
|
2015-09-13 22:07:54 -04:00
|
|
|
@expose("/dashboard/<id_>/")
|
|
|
|
def dashboard(self, id_):
|
|
|
|
session = db.session()
|
|
|
|
dashboard = (
|
|
|
|
session
|
|
|
|
.query(models.Dashboard)
|
|
|
|
.filter(models.Dashboard.id == id_)
|
|
|
|
.first()
|
|
|
|
)
|
2015-09-19 02:48:41 -04:00
|
|
|
pos_dict = {}
|
|
|
|
if dashboard.position_json:
|
|
|
|
pos_dict = {
|
|
|
|
int(o['slice_id']):o for o in json.loads(dashboard.position_json)}
|
2015-09-13 22:07:54 -04:00
|
|
|
return self.render_template(
|
2015-09-17 21:06:03 -04:00
|
|
|
"panoramix/dashboard.html", dashboard=dashboard,
|
|
|
|
pos_dict=pos_dict)
|
2015-09-13 02:25:43 -04:00
|
|
|
|
2015-07-23 02:17:51 -04:00
|
|
|
@has_access
|
2015-07-15 13:12:32 -04:00
|
|
|
@expose("/refresh_datasources/")
|
2015-07-15 20:38:03 -04:00
|
|
|
def refresh_datasources(self):
|
2015-07-30 02:39:30 -04:00
|
|
|
session = db.session()
|
|
|
|
for cluster in session.query(models.Cluster).all():
|
|
|
|
cluster.refresh_datasources()
|
|
|
|
cluster.metadata_last_refreshed = datetime.now()
|
|
|
|
flash(
|
|
|
|
"Refreshed metadata from cluster "
|
|
|
|
"[" + cluster.cluster_name + "]",
|
|
|
|
'info')
|
|
|
|
session.commit()
|
2015-07-15 13:12:32 -04:00
|
|
|
return redirect("/datasourcemodelview/list/")
|
|
|
|
|
2015-07-27 01:25:32 -04:00
|
|
|
@expose("/autocomplete/<datasource>/<column>/")
|
|
|
|
def autocomplete(self, datasource, column):
|
|
|
|
client = utils.get_pydruid_client()
|
|
|
|
top = client.topn(
|
|
|
|
datasource=datasource,
|
|
|
|
granularity='all',
|
|
|
|
intervals='2013-10-04/2020-10-10',
|
|
|
|
aggregations={"count": doublesum("count")},
|
|
|
|
dimension=column,
|
|
|
|
metric='count',
|
|
|
|
threshold=1000,
|
|
|
|
)
|
|
|
|
values = sorted([d[column] for d in top[0]['result']])
|
|
|
|
return json.dumps(values)
|
|
|
|
|
2015-07-15 13:12:32 -04:00
|
|
|
appbuilder.add_view_no_menu(Panoramix)
|
|
|
|
appbuilder.add_link(
|
2015-08-01 20:16:39 -04:00
|
|
|
"Refresh Druid Metadata",
|
2015-07-15 13:12:32 -04:00
|
|
|
href='/panoramix/refresh_datasources/',
|
2015-09-19 02:48:41 -04:00
|
|
|
category='Sources',
|
2015-09-21 00:56:23 -04:00
|
|
|
category_icon='fa-database',
|
2015-08-01 20:16:39 -04:00
|
|
|
icon="fa-cog")
|