Bugfixes+ better docs

This commit is contained in:
Maxime Beauchemin 2015-09-21 13:48:02 -07:00
parent 2cbe25c6b5
commit e1b3c7e63b
9 changed files with 100 additions and 79 deletions

View File

@ -4,6 +4,31 @@ Panoramix
Panoramix is a data exploration platform designed to be visual, intuitive
and interactive.
![img](http://i.imgur.com/aOaH0ty.png)
Panoramix
---------
Panoramix's main goal is to make it easy to slice, dice and visualize data.
It empowers its user to perform **analytics at the speed of thought**.
Panoramix provides:
* A quick way to intuitively visualize datasets
* Create and share simple dashboards
* A rich set of visualizations to analyze your data, as well as a flexible
way to extend the capabilities
* An extensible, high granularity security model allowing intricate rules
on who can access which features, and integration with major
authentication providers (database, OpenID, LDAP, OAuth & REMOTE_USER
through Flask AppBuiler)
* A simple semantic layer, allowing to control how data sources are
displayed in the UI,
by defining which fields should show up in which dropdown and which
aggregation and function (metrics) are made available to the user
* Deep integration with Druid allows for Panoramix to stay blazing fast while
slicing and dicing large, realtime datasets
Buzz Phrases
------------
@ -12,17 +37,16 @@ Buzz Phrases
* Realtime analytics when querying [Druid.io](http://druid.io)
* Extentsible to infinity
![img](http://i.imgur.com/aOaH0ty.png)
Database Support
----------------
Panoramix was originally designed on to of Druid.io, but quickly broadened
Panoramix was originally designed on to of Druid.io, but quickly broadened
its scope to support other databases through the use of SqlAlchemy, a Python
ORM that is compatible with
[many external databases](http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html).
ORM that is compatible with
[most common databases](http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html).
What's Druid?
What is Druid?
-------------
From their website at http://druid.io
@ -33,27 +57,6 @@ and fast data aggregation. Existing Druid deployments have scaled to
trillions of events and petabytes of data. Druid is best used to
power analytic dashboards and applications.*
Panoramix
---------
Panoramix's main goal is to make it easy to slice, dice and visualize data
out of Druid. It empowers its user to perform **analytics
at the speed of thought**.
Panoramix started as a hackathon project at Airbnb in while running a POC
(proof of concept) on using Druid.
Panoramix provides:
* A way to query intuitively a Druid dataset, allowing for grouping, filtering
limiting and defining a time granularity
* Many charts and visualization to analyze your data, as well as a flexible
way to extend the visualization capabilities
* An extensible, high granularity security model allowing intricate rules
on who can access which features, and integration with major
authentication providers (through Flask AppBuiler)
* A simple semantic layer, allowing to control how Druid datasources are
displayed in the UI,
by defining which fields should show up in which dropdown and which
aggregation and function (metrics) are made available to the user
Installation
------------

View File

@ -60,8 +60,8 @@ def load_examples():
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":
for i, (state, year, name, gender, num) in enumerate(bb_csv):
if i == 0:
continue
if num == "NA":
num = 0
@ -71,8 +71,8 @@ def load_examples():
state=state, year=year,
ds=ds,
name=name, num=num, gender=gender))
if i % 1000 == 0:
print("{} loaded out of 502619 rows".format(i))
if i % 5000 == 0:
print("{} loaded out of 82527 rows".format(i))
session.commit()
session.commit()
print("Done loading table!")

Binary file not shown.

View File

@ -80,7 +80,10 @@ class Slice(Model, AuditMixinNullable):
@property
def slice_url(self):
d = json.loads(self.params)
try:
d = json.loads(self.params)
except Exception as e:
d = {}
from werkzeug.urls import Href
href = Href(
"/panoramix/datasource/{self.datasource_type}/"

View File

@ -105,6 +105,7 @@
<hr style="margin-bottom: 0px;">
<img src="{{ url_for("static", filename="tux_panoramix.png") }}" width=250>
<input type="hidden" id="slice_name" name="slice_name" value="TEST">
<input type="hidden" id="action" name="action" value="">
<input type="hidden" name="datasource_name" value="{{ datasource.name }}">
<input type="hidden" name="datasource_id" value="{{ datasource.id }}">
<input type="hidden" name="datasource_type" value="{{ datasource.type }}">
@ -193,14 +194,8 @@
$(this).parent().parent().slideUp("slow", function(){$(this).remove()});
});
}
$("#plus").click(add_filter);
$("#save").click(function () {
var slice_name = prompt("Name your slice!");
$("#slice_name").val(slice_name);
$.get( "/panoramix/save/", $("#query").serialize() );
})
add_filter();
$("#druidify").click(function () {
function druidify(){
var i = 1;
// removing empty filters
@ -224,7 +219,19 @@
i++;
});
$("#query").submit();
});
}
$("#plus").click(add_filter);
$("#save").click(function () {
var slice_name = prompt("Name your slice!");
if (slice_name != "" && slice_name != null) {
$("#slice_name").val(slice_name);
$("#action").val("save");
druidify();
}
})
add_filter();
$("#druidify").click(druidify);
function create_choices (term, data) {
if ($(data).filter(function() {

View File

@ -36,14 +36,12 @@ var render = function(){
data = example_data();
data = json.data;
var compare_suffix = ' ' + json.compare_suffix;
console.log(data);
var v_compare = null;
var v = data[data.length -1][1];
if (json.compare_lag >0){
pos = data.length - (json.compare_lag+1);
if(pos >=0)
v_compare = 1-(v / data[pos][1]);
console.log(v_compare)
}
var date_ext = d3.extent(data, function(d){return d[0]});
var value_ext = d3.extent(data, function(d){return d[1]});

View File

@ -34,6 +34,7 @@
{% macro viz_js(viz) %}
{% if viz.args.get("async") != "true" %}
<script>
$( document ).ready(function() {
var url = "{{ viz.get_url(async="true", standalone="true", skip_libs="true")|safe }}";
console.log(url);
var token = $("#{{ viz.token }}");
@ -44,7 +45,7 @@
token.show();
}
else{
var table = $('table').DataTable({
var table = token.find('table').DataTable({
paging: false,
searching: false,
});
@ -53,6 +54,7 @@
token.show();
token.parent().find("img.loading").hide();
});
});
</script>
{% endif %}
{% endmacro %}

View File

@ -144,6 +144,7 @@ appbuilder.add_view(
class SliceModelView(ModelView, DeleteMixin):
datamodel = SQLAInterface(models.Slice)
can_add = False
list_columns = ['slice_link', 'viz_type', 'datasource', 'created_by']
edit_columns = [
'slice_name', 'viz_type', 'druid_datasource', 'table',
@ -211,6 +212,41 @@ class Panoramix(BaseView):
@has_access
@expose("/datasource/<datasource_type>/<datasource_id>/")
def datasource(self, datasource_type, datasource_id):
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')
if datasource_type == 'druid':
druid_datasource_id = request.args.get('datasource_id')
else:
table_id = request.args.get('datasource_id')
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)
if datasource_type == "table":
datasource = (
db.session
@ -249,15 +285,13 @@ class Panoramix(BaseView):
status=status,
mimetype="application/json")
else:
#try:
resp = 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
@ -279,36 +313,6 @@ class Panoramix(BaseView):
@has_access
@expose("/save/")
def save(self):
session = db.session()
d = request.args.to_dict(flat=False)
as_list = ('metrics', 'groupby')
for m in as_list:
v = d.get(m)
if v and not isinstance(d[m], list):
d[m] = [d[m]]
table_id = druid_datasource_id = None
datasource_type = request.args.get('datasource_type')
if datasource_type == 'druid':
druid_datasource_id = request.args.get('datasource_id')
else:
table_id = request.args.get('datasource_id')
obj = models.Slice(
params=json.dumps(d, indent=4),
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=request.args.get('slice_name', 'junk'),
)
session.add(obj)
session.commit()
session.close()
return "super!"
@has_access
@expose("/dashboard/<id_>/")

View File

@ -49,6 +49,8 @@ class BaseViz(object):
def get_url(self, **kwargs):
d = self.args.copy()
if 'action' in d:
del d['action']
d.update(kwargs)
href = Href(
'/panoramix/datasource/{self.datasource.type}/'
@ -335,7 +337,9 @@ class DistributionPieViz(HighchartsViz):
verbose_name = "Distribution - Pie Chart"
chart_type = "pie"
js_files = ['highstock.js']
form_fields = BaseViz.form_fields + ['limit']
form_fields = [
'viz_type', 'metrics', 'groupby',
('since', 'until'), 'limit']
def query_obj(self):
d = super(DistributionPieViz, self).query_obj()