From a5b896414d1ae73783c9a26bd74342bd4c05d75f Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Sun, 13 Sep 2015 19:07:54 -0700 Subject: [PATCH] Dashboards --- TODO.md | 2 - panoramix/models.py | 65 ++++++++++++++++++- panoramix/templates/panoramix/dashboard.html | 47 ++++++++------ panoramix/templates/panoramix/viz.html | 15 +++++ .../templates/panoramix/viz_highcharts.html | 6 -- panoramix/templates/panoramix/viz_table.html | 26 +++----- panoramix/views.py | 31 +++++++-- panoramix/viz.py | 7 ++ 8 files changed, 147 insertions(+), 52 deletions(-) diff --git a/TODO.md b/TODO.md index 40c646ac8..e15ee80c4 100644 --- a/TODO.md +++ b/TODO.md @@ -3,8 +3,6 @@ * DRUID: Allow for post aggregations (ratios!) * compare time ranges * csv export out of table view -* Save / bookmark / url shortener * SQL: Find a way to manage granularity * Create ~/.panoramix/ to host DB and config, generate default config there * Add a per-datasource permission - diff --git a/panoramix/models.py b/panoramix/models.py index b3294ae1a..4eb47a5a9 100644 --- a/panoramix/models.py +++ b/panoramix/models.py @@ -10,7 +10,7 @@ from sqlalchemy import ( Column, Integer, String, ForeignKey, Text, Boolean, DateTime) from panoramix.utils import JSONEncodedDict from sqlalchemy import Table as sqlaTable -from sqlalchemy import create_engine, MetaData, desc, select, and_ +from sqlalchemy import create_engine, MetaData, desc, select, and_, Table from sqlalchemy.orm import relationship from sqlalchemy.sql import table, literal_column, text @@ -33,12 +33,23 @@ class Slice(Model, AuditMixin): __tablename__ = 'slices' id = Column(Integer, primary_key=True) slice_name = Column(String(250)) - datasource_id = Column(Integer) + datasource_id = Column(Integer, ForeignKey('datasources.id')) + table_id = Column(Integer, ForeignKey('tables.id')) datasource_type = Column(String(200)) datasource_name = Column(String(2000)) viz_type = Column(String(250)) params = Column(Text) + table = relationship('Table', backref='slices') + druid_datasource = relationship('Datasource', backref='slices') + + def __repr__(self): + return self.slice_name + + @property + def datasource(self): + return self.table or self.druid_datasource + @property def slice_link(self): d = json.loads(self.params) @@ -48,6 +59,53 @@ class Slice(Model, AuditMixin): "{self.datasource_id}/?{kwargs}").format(**locals()) return '{self.slice_name}'.format(**locals()) + @property + def js_files(self): + from panoramix.viz import viz_types + return viz_types[self.viz_type].js_files + + @property + def css_files(self): + from panoramix.viz import viz_types + return viz_types[self.viz_type].css_files + + +dashboard_slices = Table('dashboard_slices', Model.metadata, + Column('id', Integer, primary_key=True), + Column('dashboard_id', Integer, ForeignKey('dashboards.id')), + Column('slice_id', Integer, ForeignKey('slices.id')), +) + + +class Dashboard(Model, AuditMixin): + """A dash to slash""" + __tablename__ = 'dashboards' + id = Column(Integer, primary_key=True) + dashboard_title = Column(String(500)) + slices = relationship( + 'Slice', secondary=dashboard_slices, backref='dashboards') + + def __repr__(self): + return self.dashboard_title + + def dashboard_link(self): + url = "/panoramix/dashboard/{}/".format(self.id) + return '{self.dashboard_title}'.format(**locals()) + + @property + def js_files(self): + l = [] + for o in self.slices: + l += o.js_files + return list(set(l)) + + @property + def css_files(self): + l = [] + for o in self.slices: + l += o.css_files + return list(set(l)) + class Queryable(object): @property @@ -99,6 +157,9 @@ class Table(Model, Queryable, AuditMixin): baselink = "tableview" + def __repr__(self): + return self.table_name + @property def name(self): return self.table_name diff --git a/panoramix/templates/panoramix/dashboard.html b/panoramix/templates/panoramix/dashboard.html index c65aff43b..ad8dfa0e8 100644 --- a/panoramix/templates/panoramix/dashboard.html +++ b/panoramix/templates/panoramix/dashboard.html @@ -1,7 +1,7 @@ {% extends "panoramix/base.html" %} {% block head_css %} - {{super()}} + {{ super() }} {% endblock %} {% block content_fluid %} +

{{ dashboard.dashboard_title }}

{% endblock %} @@ -48,15 +52,16 @@ {{ super() }} {% endblock %} diff --git a/panoramix/templates/panoramix/viz.html b/panoramix/templates/panoramix/viz.html index f4803c8f9..eec9f330d 100644 --- a/panoramix/templates/panoramix/viz.html +++ b/panoramix/templates/panoramix/viz.html @@ -3,3 +3,18 @@ {% else %} {% extends 'panoramix/datasource.html' %} {% endif %} + +{% block head %} + {{super()}} + {% for css in viz.css_files %} + + {% endfor %} +{% endblock %} + + +{% block tail %} + {{super()}} + {% for js in viz.js_files %} + + {% endfor %} +{% endblock %} diff --git a/panoramix/templates/panoramix/viz_highcharts.html b/panoramix/templates/panoramix/viz_highcharts.html index ae10b83e7..5d815d464 100644 --- a/panoramix/templates/panoramix/viz_highcharts.html +++ b/panoramix/templates/panoramix/viz_highcharts.html @@ -6,12 +6,6 @@ {% block tail %} {{ super() }} - {% if viz.stockchart %} - - {% else %} - - {% endif %} - - - - + {% endblock %} diff --git a/panoramix/views.py b/panoramix/views.py index b3a3f1815..1be93d7a7 100644 --- a/panoramix/views.py +++ b/panoramix/views.py @@ -106,7 +106,7 @@ appbuilder.add_view( class SliceModelView(ModelView, DeleteMixin): datamodel = SQLAInterface(models.Slice) - list_columns = ['slice_link', 'viz_type', 'created_by'] + list_columns = ['slice_link', 'viz_type', 'datasource', 'created_by'] appbuilder.add_view( SliceModelView, @@ -116,6 +116,21 @@ appbuilder.add_view( category_icon='',) +class DashboardModelView(ModelView, DeleteMixin): + datamodel = SQLAInterface(models.Dashboard) + list_columns = ['dashboard_link', 'created_by'] + edit_columns = ['dashboard_title', 'slices',] + add_columns = edit_columns + + +appbuilder.add_view( + DashboardModelView, + "Dashboards", + icon="fa-dashboard", + category="", + category_icon='',) + + class DatabaseView(ModelView, DeleteMixin): datamodel = SQLAInterface(models.Database) list_columns = ['database_name'] @@ -258,9 +273,17 @@ class Panoramix(BaseView): return "super!" @has_access - @expose("/dashboard/") - def dashboard(self): - return self.render_template("panoramix/dashboard.html") + @expose("/dashboard//") + def dashboard(self, id_): + session = db.session() + dashboard = ( + session + .query(models.Dashboard) + .filter(models.Dashboard.id == id_) + .first() + ) + return self.render_template( + "panoramix/dashboard.html", dashboard=dashboard) @has_access @expose("/refresh_datasources/") diff --git a/panoramix/viz.py b/panoramix/viz.py index cc8f6760a..4a38b8f79 100644 --- a/panoramix/viz.py +++ b/panoramix/viz.py @@ -24,6 +24,8 @@ class BaseViz(object): form_fields = [ 'viz_type', 'metrics', 'groupby', 'granularity', ('since', 'until')] + js_files = [] + css_files = [] def __init__(self, datasource, form_data, view): self.datasource = datasource @@ -137,6 +139,8 @@ class TableViz(BaseViz): verbose_name = "Table View" template = 'panoramix/viz_table.html' form_fields = BaseViz.form_fields + ['row_limit'] + css_files = ['dataTables.bootstrap.css'] + js_files = ['jquery.dataTables.min.js', 'dataTables.bootstrap.js'] def query_obj(self): d = super(TableViz, self).query_obj() @@ -165,6 +169,7 @@ class HighchartsViz(BaseViz): stacked = False chart_type = 'not_stock' compare = False + js_files = ['highcharts.js'] class BubbleViz(HighchartsViz): @@ -174,6 +179,7 @@ class BubbleViz(HighchartsViz): form_fields = [ 'viz_type', 'since', 'until', 'series', 'entity', 'x', 'y', 'size', 'limit'] + js_files = ['highcharts.js', 'highcharts-more.js'] def query_obj(self): d = super(BubbleViz, self).query_obj() @@ -212,6 +218,7 @@ class TimeSeriesViz(HighchartsViz): chart_type = "spline" stockchart = True sort_legend_y = True + js_files = ['highstock.js', 'highcharts-more.js'] form_fields = [ 'viz_type', 'granularity', ('since', 'until'),