Adding a command to load a sample dataset

This commit is contained in:
Maxime Beauchemin 2015-09-18 23:48:41 -07:00
parent 4edbbd350d
commit e0d6d20993
7 changed files with 180 additions and 76 deletions

View File

@ -7,7 +7,7 @@ and interactive.
Buzz Phrases
------------
* Analytics at the speed of thought!
* Analytics at the speed of thought!
* Instantaneous learning curve
* Realtime analytics when querying [Druid.io](http://druid.io)
* Extentsible to infinity
@ -67,11 +67,11 @@ pip install panoramix
# Create an admin user
fabmanager create-admin --app panoramix
# Clone the github repo
git clone https://github.com/mistercrunch/panoramix.git
# Load some data to play with
panoramix load_examples
# Start the web server
panoramix
# Start the development web server
panoramix runserver -d
```
After installation, you should be able to point your browser to the right

View File

@ -4,6 +4,12 @@ from flask.ext.script import Manager
from panoramix import app, config
from subprocess import Popen
from flask.ext.migrate import MigrateCommand
from panoramix import db
from flask.ext.appbuilder import Base
from sqlalchemy import Column, Integer, String
from panoramix import config, models
import csv
import gzip
manager = Manager(app)
@ -33,9 +39,66 @@ def runserver(debug, port):
Popen(cmd, shell=True).wait()
@manager.command
def load_examples(self):
def load_examples():
"""Loads a set of Slices and Dashboards and a supporting dataset """
print("Loading examples")
print("Loading examples into {}".format(db))
class BirthNames(Base):
__tablename__ = "birth_names"
id = Column(Integer, primary_key=True)
state = Column(String(10))
year = Column(Integer)
name = Column(String(128))
num = Column(Integer)
ds = Column(String(20))
gender = Column(String(10))
try:
BirthNames.__table__.drop(db.engine)
except:
pass
Base.metadata.create_all(db.engine)
session = db.session()
with gzip.open(config.basedir + '/data/birth_names.csv.gz') as f:
bb_csv = csv.reader(f)
for i, (state, year, name, num, gender) in enumerate(bb_csv):
if i == 0 or not name or name=="\xc2\xa0":
continue
if num == "NA":
num = 0
ds = str(year) + '-01-01'
session.add(
BirthNames(
state=state, year=year,
ds=ds,
name=name, num=num, gender=gender))
if i % 1000 == 0:
print("{} loaded out of 502619 rows".format(i))
session.commit()
session.commit()
print("Done loading table!")
DB = models.Database
dbobj = session.query(DB).filter_by(database_name='main').first()
if not dbobj:
dbobj = DB()
dbobj.database_name = "main"
dbobj.sqlalchemy_uri = config.SQLALCHEMY_DATABASE_URI
session.merge(dbobj)
session.commit()
TBL = models.Table
obj = session.query(TBL).filter_by(table_name='birth_names').first()
if not obj:
obj = TBL()
obj.table_name = 'birth_names'
obj.main_dttm_col = 'ds'
obj.default_endpoint = "/panoramix/datasource/table/1/?viz_type=table&granularity=one+day&since=100+years&until=now&row_limit=10&where=&flt_col_0=ds&flt_op_0=in&flt_eq_0=&flt_col_1=ds&flt_op_1=in&flt_eq_1=&slice_name=TEST&datasource_name=birth_names&datasource_id=1&datasource_type=table"
obj.database = dbobj
obj.fetch_metadata()
session.merge(obj)
session.commit()
session.close()
if __name__ == "__main__":
manager.run()

Binary file not shown.

View File

@ -24,11 +24,23 @@ import textwrap
from panoramix import db, get_session, config, utils
from panoramix.viz import viz_types
from sqlalchemy.ext.declarative import declared_attr
QueryResult = namedtuple('namedtuple', ['df', 'query', 'duration'])
class Slice(Model, AuditMixin):
class AuditMixinNullable(AuditMixin):
@declared_attr
def created_by_fk(cls):
return Column(Integer, ForeignKey('ab_user.id'),
default=cls.get_user_id, nullable=True)
@declared_attr
def changed_by_fk(cls):
return Column(Integer, ForeignKey('ab_user.id'),
default=cls.get_user_id, onupdate=cls.get_user_id, nullable=True)
class Slice(Model, AuditMixinNullable):
"""A slice is essentially a report or a view on data"""
__tablename__ = 'slices'
id = Column(Integer, primary_key=True)
@ -75,6 +87,10 @@ class Slice(Model, AuditMixin):
"{self.datasource_id}/".format(self=self))
return href(d)
@property
def edit_url(self):
return "/slicemodelview/edit/{}".format(self.id)
@property
def slice_link(self):
url = self.slice_url
@ -101,7 +117,7 @@ dashboard_slices = Table('dashboard_slices', Model.metadata,
)
class Dashboard(Model, AuditMixin):
class Dashboard(Model, AuditMixinNullable):
"""A dash to slash"""
__tablename__ = 'dashboards'
id = Column(Integer, primary_key=True)
@ -146,7 +162,7 @@ class Queryable(object):
return sorted([c.column_name for c in self.columns if c.filterable])
class Database(Model, AuditMixin):
class Database(Model, AuditMixinNullable):
__tablename__ = 'dbs'
id = Column(Integer, primary_key=True)
database_name = Column(String(250), unique=True)
@ -166,15 +182,13 @@ class Database(Model, AuditMixin):
autoload_with=self.get_sqla_engine())
class Table(Model, Queryable, AuditMixin):
class Table(Model, Queryable, AuditMixinNullable):
type = "table"
__tablename__ = 'tables'
id = Column(Integer, primary_key=True)
table_name = Column(String(255), unique=True)
main_datetime_column_id = Column(Integer, ForeignKey('table_columns.id'))
main_datetime_column = relationship(
'TableColumn', foreign_keys=[main_datetime_column_id])
table_name = Column(String(250), unique=True)
main_dttm_col = Column(String(250))
default_endpoint = Column(Text)
database_id = Column(Integer, ForeignKey('dbs.id'), nullable=False)
database = relationship(
@ -308,8 +322,11 @@ class Table(Model, Queryable, AuditMixin):
extras=None):
qry_start_dttm = datetime.now()
if not self.main_dttm_col:
raise Exception(
"Datetime column not provided as part table configuration")
timestamp = literal_column(
self.main_datetime_column.column_name).label('timestamp')
self.main_dttm_col).label('timestamp')
metrics_exprs = [
literal_column(m.expression).label(m.metric_name)
for m in self.metrics if m.metric_name in metrics]
@ -420,7 +437,7 @@ class Table(Model, Queryable, AuditMixin):
self.columns.append(dbcol)
if not any_date_col and 'date' in datatype.lower():
any_date_col = dbcol
any_date_col = col.name
if dbcol.sum:
metrics.append(M(
@ -464,18 +481,18 @@ class Table(Model, Queryable, AuditMixin):
m = (
db.session.query(M)
.filter(M.metric_name == metric.metric_name)
.filter(M.table == self)
.filter(M.table_id == self.id)
.first()
)
metric.table = self
metric.table_id = self.id
if not m:
db.session.add(metric)
db.session.commit()
if not self.main_datetime_column:
self.main_datetime_column = any_date_col
if not self.main_dttm_col:
self.main_dttm_col = any_date_col
class SqlMetric(Model, AuditMixin):
class SqlMetric(Model, AuditMixinNullable):
__tablename__ = 'sql_metrics'
id = Column(Integer, primary_key=True)
metric_name = Column(String(512))
@ -488,7 +505,7 @@ class SqlMetric(Model, AuditMixin):
description = Column(Text)
class TableColumn(Model, AuditMixin):
class TableColumn(Model, AuditMixinNullable):
__tablename__ = 'table_columns'
id = Column(Integer, primary_key=True)
table_id = Column(Integer, ForeignKey('tables.id'))
@ -513,7 +530,7 @@ class TableColumn(Model, AuditMixin):
return self.type in ('LONG', 'DOUBLE', 'FLOAT')
class Cluster(Model, AuditMixin):
class Cluster(Model, AuditMixinNullable):
__tablename__ = 'clusters'
id = Column(Integer, primary_key=True)
cluster_name = Column(String(250), unique=True)
@ -560,7 +577,7 @@ class Datasource(Model, AuditMixin, Queryable):
user_id = Column(Integer, ForeignKey('ab_user.id'))
owner = relationship('User', backref='datasources', foreign_keys=[user_id])
cluster_name = Column(
String(255), ForeignKey('clusters.cluster_name'))
String(250), ForeignKey('clusters.cluster_name'))
cluster = relationship(
'Cluster', backref='datasources', foreign_keys=[cluster_name])
@ -783,7 +800,7 @@ class Metric(Model):
return obj
class Column(Model, AuditMixin):
class Column(Model, AuditMixinNullable):
__tablename__ = 'columns'
id = Column(Integer, primary_key=True)
datasource_name = Column(

View File

@ -95,7 +95,7 @@
<div class="col-md-4 text-right" style="position: relative;">
<a href="{{ slice.slice_url }}"><i class="fa fa-play"></i></a>
<a class="refresh"><i class="fa fa-refresh"></i></a>
<a><i class="fa fa-gear"></i></a>
<a href="{{ slice.edit_url }}"><i class="fa fa-gear"></i></a>
<a class="closewidget"><i class="fa fa-close"></i></a>
</div>
</div>

View File

@ -26,8 +26,7 @@
</tbody>
</table>
{% else %}
<div id="{{ viz.token }}" style="display: none;">
</div>
<div id="{{ viz.token }}" style="display: none;"></div>
<img src="{{ url_for("static", filename="loading.gif") }}" class="loading">
{% endif %}
{% endmacro %}
@ -37,14 +36,22 @@
<script>
var url = "{{ viz.get_url(async="true", standalone="true", skip_libs="true")|safe }}";
console.log(url);
$("#{{ viz.token }}").load(url, function(){
var table = $('table').DataTable({
paging: false,
searching: false,
});
$("#{{ viz.token }}").show();
table.column('-1').order( 'desc' ).draw();
$("#{{ viz.token }} img.loading").hide();
var token = $("#{{ viz.token }}");
token.load(url, function(response, status, xhr){
if(status=="error"){
var err = '<div class="alert alert-danger">' + xhr.responseText + '</div>';
token.html(err);
token.show();
}
else{
var table = $('table').DataTable({
paging: false,
searching: false,
});
table.column('-1').order( 'desc' ).draw();
}
token.show();
token.parent().find("img.loading").hide();
});
</script>
{% endif %}

View File

@ -86,6 +86,44 @@ class MetricInlineView(CompactCRUDMixin, ModelView):
appbuilder.add_view_no_menu(MetricInlineView)
class DatabaseView(ModelView, DeleteMixin):
datamodel = SQLAInterface(models.Database)
list_columns = ['database_name']
add_columns = ['database_name', 'sqlalchemy_uri']
edit_columns = add_columns
appbuilder.add_view(
DatabaseView,
"Databases",
icon="fa-database",
category="Sources",
category_icon='fa-cogs',)
class TableView(ModelView, DeleteMixin):
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")
class ClusterModelView(ModelView, DeleteMixin):
datamodel = SQLAInterface(models.Cluster)
add_columns = [
@ -100,13 +138,16 @@ appbuilder.add_view(
ClusterModelView,
"Druid Clusters",
icon="fa-server",
category="Admin",
category="Sources",
category_icon='fa-cogs',)
class SliceModelView(ModelView, DeleteMixin):
datamodel = SQLAInterface(models.Slice)
list_columns = ['slice_link', 'viz_type', 'datasource', 'created_by']
edit_columns = [
'slice_name', 'viz_type', 'druid_datasource', 'table',
'dashboards', 'params']
appbuilder.add_view(
SliceModelView,
@ -131,40 +172,6 @@ appbuilder.add_view(
category_icon='',)
class DatabaseView(ModelView, DeleteMixin):
datamodel = SQLAInterface(models.Database)
list_columns = ['database_name']
add_columns = ['database_name', 'sqlalchemy_uri']
edit_columns = add_columns
appbuilder.add_view(
DatabaseView,
"Databases",
icon="fa-database",
category="Admin",
category_icon='fa-cogs',)
class TableView(ModelView, DeleteMixin):
datamodel = SQLAInterface(models.Table)
list_columns = ['table_link', 'database']
add_columns = ['table_name', 'database', 'default_endpoint']
edit_columns = [
'table_name', 'database', 'main_datetime_column', '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",
icon='fa-table',)
class DatasourceModelView(ModelView, DeleteMixin):
datamodel = SQLAInterface(models.Datasource)
list_columns = [
@ -186,6 +193,7 @@ class DatasourceModelView(ModelView, DeleteMixin):
appbuilder.add_view(
DatasourceModelView,
"Druid Datasources",
category="Sources",
icon="fa-cubes")
@ -241,7 +249,14 @@ class Panoramix(BaseView):
status=status,
mimetype="application/json")
else:
return self.render_template("panoramix/viz.html", viz=obj)
try:
resp = self.render_template("panoramix/viz.html", viz=obj)
except Exception as e:
return Response(
str(e),
status=500,
mimetype="application/json")
return resp
@has_access
@expose("/save_dash/<dashboard_id>/", methods=['GET', 'POST'])
@ -303,8 +318,10 @@ class Panoramix(BaseView):
.filter(models.Dashboard.id == id_)
.first()
)
pos_dict = {
int(o['slice_id']):o for o in json.loads(dashboard.position_json)}
pos_dict = {}
if dashboard.position_json:
pos_dict = {
int(o['slice_id']):o for o in json.loads(dashboard.position_json)}
return self.render_template(
"panoramix/dashboard.html", dashboard=dashboard,
pos_dict=pos_dict)
@ -342,6 +359,6 @@ appbuilder.add_view_no_menu(Panoramix)
appbuilder.add_link(
"Refresh Druid Metadata",
href='/panoramix/refresh_datasources/',
category='Admin',
category='Sources',
category_icon='fa-cogs',
icon="fa-cog")