mirror of https://github.com/apache/superset.git
Merge pull request #120 from mistercrunch/sliceinfo
Slice information can be displayed in dashboard
This commit is contained in:
commit
ef612ed66c
1
TODO.md
1
TODO.md
|
@ -20,6 +20,7 @@ List of TODO items for Panoramix
|
|||
An example of a layer might be "holidays" or "site outages", ...
|
||||
* **Worth doing? User defined groups:** People could define mappings in the UI of say "Countries I follow" and apply it to different datasets. For now, this is done by writing CASE-WHEN-type expression which is probably good enough.
|
||||
|
||||
|
||||
## Easy-ish fix
|
||||
* datasource in explore mode could be a dropdown
|
||||
* Create a set of slices and dashboard on top of the World Bank dataset that ship with load_examples
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 43df8de3a5f4
|
||||
Revises: 7dbf98566af7
|
||||
Create Date: 2016-01-18 23:43:16.073483
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '43df8de3a5f4'
|
||||
down_revision = '7dbf98566af7'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
def upgrade():
|
||||
op.add_column('dashboards', sa.Column('json_metadata', sa.Text(), nullable=True))
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('dashboards', 'json_metadata')
|
|
@ -0,0 +1,21 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 7dbf98566af7
|
||||
Revises: 8e80a26a31db
|
||||
Create Date: 2016-01-17 22:00:23.640788
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7dbf98566af7'
|
||||
down_revision = '8e80a26a31db'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
def upgrade():
|
||||
op.add_column('slices', sa.Column('description', sa.Text(), nullable=True))
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('slices', 'description')
|
|
@ -66,6 +66,7 @@ class Slice(Model, AuditMixinNullable):
|
|||
datasource_name = Column(String(2000))
|
||||
viz_type = Column(String(250))
|
||||
params = Column(Text)
|
||||
description = Column(Text)
|
||||
|
||||
table = relationship(
|
||||
'SqlaTable', foreign_keys=[table_id], backref='slices')
|
||||
|
@ -88,6 +89,10 @@ class Slice(Model, AuditMixinNullable):
|
|||
form_data=d)
|
||||
return viz
|
||||
|
||||
@property
|
||||
def description_markeddown(self):
|
||||
return utils.markdown(self.description)
|
||||
|
||||
@property
|
||||
def datasource_id(self):
|
||||
return self.table_id or self.druid_datasource_id
|
||||
|
@ -154,6 +159,7 @@ class Dashboard(Model, AuditMixinNullable):
|
|||
position_json = Column(Text)
|
||||
description = Column(Text)
|
||||
css = Column(Text)
|
||||
json_metadata = Column(Text)
|
||||
slug = Column(String(255), unique=True)
|
||||
slices = relationship(
|
||||
'Slice', secondary=dashboard_slices, backref='dashboards')
|
||||
|
@ -165,6 +171,10 @@ class Dashboard(Model, AuditMixinNullable):
|
|||
def url(self):
|
||||
return "/panoramix/dashboard/{}/".format(self.slug or self.id)
|
||||
|
||||
@property
|
||||
def metadata_dejson(self):
|
||||
return json.loads(self.json_metadata)
|
||||
|
||||
def dashboard_link(self):
|
||||
return '<a href="{self.url}">{self.dashboard_title}</a>'.format(self=self)
|
||||
|
||||
|
|
|
@ -11,7 +11,18 @@ html>body{
|
|||
margin-left: 365px;
|
||||
}
|
||||
|
||||
.slice_description{
|
||||
padding: 8px;
|
||||
margin: 5px;
|
||||
border: 1px solid #DDD;
|
||||
background-color: #F8F8F8;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.slice_info{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.padded{
|
||||
padding: 10px;
|
||||
|
@ -23,7 +34,7 @@ html>body{
|
|||
}
|
||||
|
||||
.slice_container {
|
||||
height: 100%;
|
||||
//height: 100%;
|
||||
}
|
||||
.container-fluid {
|
||||
text-align: left;
|
||||
|
|
|
@ -106,7 +106,13 @@ var px = (function() {
|
|||
return token.width();
|
||||
},
|
||||
height: function(){
|
||||
return token.height() - 25;
|
||||
var others = 0;
|
||||
var widget = container.parents('.widget');
|
||||
var slice_description = widget.find('.slice_description');
|
||||
if (slice_description.is(":visible"))
|
||||
others += widget.find('.slice_description').height() + 25;
|
||||
others += widget.find('.slice_header').height();
|
||||
return widget.height() - others;
|
||||
},
|
||||
render: function() {
|
||||
$('.btn-group.results span').attr('disabled','disabled');
|
||||
|
@ -416,9 +422,17 @@ var px = (function() {
|
|||
}).data('gridster');
|
||||
$("div.gridster").css('visibility', 'visible');
|
||||
$("#savedash").click(function() {
|
||||
var expanded_slices = {};
|
||||
$.each($(".slice_info"), function(i, d){
|
||||
var widget = $(this).parents('.widget');
|
||||
var slice_description = widget.find('.slice_description');
|
||||
if(slice_description.is(":visible"))
|
||||
expanded_slices[$(d).attr('slice_id')] = true;
|
||||
});
|
||||
var data = {
|
||||
positions: gridster.serialize(),
|
||||
css: $("#dash_css").val()
|
||||
css: $("#dash_css").val(),
|
||||
expanded_slices: expanded_slices,
|
||||
};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
|
@ -432,6 +446,13 @@ var px = (function() {
|
|||
var li = $(this).parents("li");
|
||||
gridster.remove_widget(li);
|
||||
});
|
||||
$(".slice_info").click(function(){
|
||||
var widget = $(this).parents('.widget');
|
||||
var slice_description = widget.find('.slice_description');
|
||||
slice_description.slideToggle(500, function(){
|
||||
widget.find('.refresh').click();
|
||||
});
|
||||
});
|
||||
$("table.slice_header").mouseover(function() {
|
||||
$(this).find("td.icons nobr").show();
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ function viz_nvd3(slice) {
|
|||
$.getJSON(slice.jsonEndpoint(), function(payload) {
|
||||
var fd = payload.form_data;
|
||||
var viz_type = fd.viz_type;
|
||||
var f = d3.format('.4s');
|
||||
var f = d3.format('.3s');
|
||||
nv.addGraph(function() {
|
||||
if (viz_type === 'line') {
|
||||
if (fd.show_brush) {
|
||||
|
@ -49,6 +49,7 @@ function viz_nvd3(slice) {
|
|||
} else if (viz_type === 'pie') {
|
||||
chart = nv.models.pieChart()
|
||||
chart.showLegend(fd.show_legend);
|
||||
chart.valueFormat(f);
|
||||
if (fd.donut) {
|
||||
chart.donut(true);
|
||||
chart.donutLabelsOutside(true);
|
||||
|
@ -99,8 +100,13 @@ function viz_nvd3(slice) {
|
|||
chart.showLegend(fd.show_legend);
|
||||
}
|
||||
|
||||
// make space for labels on right
|
||||
//chart.height($(".chart").height() - 50).margin({"right": 50});
|
||||
var height = slice.height();
|
||||
if(chart.hasOwnProperty("x2Axis")) {
|
||||
height += 30;
|
||||
}
|
||||
chart.height(height);
|
||||
slice.container.css('height', height + 'px');
|
||||
|
||||
if ((viz_type === "line" || viz_type === "area") && fd.rich_tooltip) {
|
||||
chart.useInteractiveGuideline(true);
|
||||
}
|
||||
|
@ -122,7 +128,9 @@ function viz_nvd3(slice) {
|
|||
else if (fd.x_axis_format !== undefined) {
|
||||
chart.xAxis.tickFormat(px.timeFormatFactory(fd.x_axis_format));
|
||||
}
|
||||
if (fd.contribution || fd.num_period_compare) {
|
||||
if (chart.yAxis !== undefined)
|
||||
chart.yAxis.tickFormat(d3.format('.3s'));
|
||||
if (fd.contribution || fd.num_period_compare || viz_type == 'compare') {
|
||||
chart.yAxis.tickFormat(d3.format('.3p'));
|
||||
if (chart.y2Axis != undefined) {
|
||||
chart.y2Axis.tickFormat(d3.format('.3p'));
|
||||
|
@ -140,15 +148,9 @@ function viz_nvd3(slice) {
|
|||
d3.select(slice.selector).append("svg")
|
||||
.datum(payload.data)
|
||||
.transition().duration(500)
|
||||
.attr('height', height)
|
||||
.call(chart);
|
||||
|
||||
// if it is a two axis chart, rescale it down just a little so it fits in the div.
|
||||
if(chart.hasOwnProperty("x2Axis")) {
|
||||
two_axis_chart = $(slice.selector + " > svg");
|
||||
w = two_axis_chart.width();
|
||||
h = two_axis_chart.height();
|
||||
two_axis_chart.get(0).setAttribute('viewBox', '0 0 '+w+' '+(h+30));
|
||||
}
|
||||
return chart;
|
||||
});
|
||||
slice.done(payload);
|
||||
|
|
|
@ -79,13 +79,13 @@ px.registerViz('table', function(slice) {
|
|||
paging: false,
|
||||
searching: form_data.include_search,
|
||||
});
|
||||
slice.container.find('.tooltip').remove();
|
||||
// Sorting table by main column
|
||||
if (form_data.metrics.length > 0) {
|
||||
var main_metric = form_data.metrics[0];
|
||||
datatable.column(data.columns.indexOf(main_metric)).order( 'desc' ).draw();
|
||||
}
|
||||
slice.done(json);
|
||||
slice.container.parents('.widget').find('.tooltip').remove();
|
||||
}).fail(function(xhr){
|
||||
slice.error(xhr.responseText);
|
||||
});
|
||||
|
|
|
@ -91,25 +91,35 @@ body {
|
|||
<nobr class="icons">
|
||||
<a><i class="fa fa-arrows drag"></i></a>
|
||||
<a class="refresh"><i class="fa fa-refresh"></i></a>
|
||||
<a class="bug" data-slice_id="{{ slice.id }}" data-toggle="tooltip" title="console.log(this.slice);"><i class="fa fa-bug"></i></a>
|
||||
</nobr>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-center header"><nobr>{{ slice.slice_name }}</nobr></div>
|
||||
<div class="text-center header">
|
||||
<nobr>
|
||||
{{ slice.slice_name }}
|
||||
{% if slice.description %}
|
||||
<i class="fa fa-info-circle slice_info" slice_id="{{ slice.id }}"></i>
|
||||
{% endif %}
|
||||
</nobr>
|
||||
</div>
|
||||
</td>
|
||||
<td class="icons text-right">
|
||||
<nobr>
|
||||
<a href="{{ slice.slice_url }}"><i class="fa fa-play"></i></a>
|
||||
<a href="{{ slice.edit_url }}"><i class="fa fa-gear"></i></a>
|
||||
<a href="{{ slice.edit_url }}"><i class="fa fa-edit"></i></a>
|
||||
<a class="closeslice"><i class="fa fa-close"></i></a>
|
||||
</br>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="{{ viz.token }}" class="token" style="height: 100%;">
|
||||
<div class="slice_description bs-callout bs-callout-default" style="{{ 'display: none;' if "{}".format(slice.id) not in dashboard.metadata_dejson.expanded_slices }}">
|
||||
{{ slice.description_markeddown | safe }}
|
||||
</div>
|
||||
<input type="hidden" slice_id="{{ slice.id }}" value="false">
|
||||
<div id="{{ viz.token }}" class="token">
|
||||
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
|
||||
<div class="slice_container" id="{{ viz.token }}_con" style="height: 100%; width: 100%;"></div>
|
||||
<div class="slice_container" id="{{ viz.token }}_con"></div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
@ -131,9 +141,6 @@ body {
|
|||
$('#filters').click( function(){
|
||||
alert(dashboard.readFilters());
|
||||
});
|
||||
$('a.bug').click( function(){
|
||||
console.log(dashboard.getSlice($(this).data('slice_id')));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -42,11 +42,16 @@
|
|||
</a>
|
||||
</span>
|
||||
<span>{{ form.get_field("viz_type")(class_="select2") }}</span>
|
||||
<span class="alert alert-info" title="Slice Name" data-toggle="tooltip">{{ viz.form_data.slice_name }}
|
||||
<a class="" href="/slicemodelview/edit/{{ viz.form_data.slice_id }}" data-toggle="tooltip" title="Edit Slice metadata">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
</span>
|
||||
{% if slice %}
|
||||
<span class="alert alert-info" title="Slice" data-toggle="tooltip">{{ slice.slice_name }}
|
||||
<a class="" href="/slicemodelview/edit/{{ slice.id }}" data-toggle="tooltip" title="Edit Slice metadata">
|
||||
{% if slice.description %}
|
||||
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="bottom" title="{{ slice.description }}"></i>
|
||||
{% endif %}
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
<div class="btn-group results pull-right" role="group">
|
||||
<a role="button" tabindex="0" class="btn btn-default" id="shortner" title="Short URL" data-toggle="popover" data-trigger="focus">
|
||||
<i class="fa fa-link"></i>
|
||||
|
@ -160,7 +165,7 @@
|
|||
{% include 'appbuilder/flash.html' %}
|
||||
<div
|
||||
id="{{ viz.token }}"
|
||||
class="viz slice {{ viz.viz_type }}"
|
||||
class="widget viz slice {{ viz.viz_type }}"
|
||||
data-slice="{{ viz.json_data }}"
|
||||
style="height: 700px;">
|
||||
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{% if viz.form_data.get("json") == "true" %}
|
||||
{{ viz.get_json() }}
|
||||
{% else %}
|
||||
|
||||
{% if viz.request.args.get("standalone") == "true" %}
|
||||
{% extends 'panoramix/standalone.html' %}
|
||||
{% else %}
|
||||
|
@ -22,4 +23,5 @@
|
|||
<script src="{{ url_for('static', filename=js) }}"></script>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% endif %}
|
||||
|
|
|
@ -205,9 +205,12 @@ class SliceModelView(PanoramixModelView, DeleteMixin):
|
|||
'slice_link', 'viz_type', 'datasource_type',
|
||||
'datasource', 'created_by', 'changed_on_']
|
||||
edit_columns = [
|
||||
'slice_name', 'viz_type', 'druid_datasource',
|
||||
'slice_name', 'description', 'viz_type', 'druid_datasource',
|
||||
'table', 'dashboards', 'params']
|
||||
base_order = ('changed_on','desc')
|
||||
description_columns = {
|
||||
'description': Markup("The content here can be displayed as widget headers in the dashboard view. Supports <a href='https://daringfireball.net/projects/markdown/'>markdown</a>"),
|
||||
}
|
||||
|
||||
|
||||
appbuilder.add_view(
|
||||
|
@ -222,7 +225,8 @@ class DashboardModelView(PanoramixModelView, DeleteMixin):
|
|||
datamodel = SQLAInterface(models.Dashboard)
|
||||
list_columns = ['dashboard_link', 'created_by', 'changed_by', 'changed_on_']
|
||||
edit_columns = [
|
||||
'dashboard_title', 'slug', 'slices', 'position_json', 'css']
|
||||
'dashboard_title', 'slug', 'slices', 'position_json', 'css',
|
||||
'json_metadata']
|
||||
add_columns = edit_columns
|
||||
base_order = ('changed_on','desc')
|
||||
description_columns = {
|
||||
|
@ -434,6 +438,15 @@ class Panoramix(BaseView):
|
|||
payload,
|
||||
status=status,
|
||||
mimetype="application/csv")
|
||||
|
||||
slice_id = request.args.get("slice_id")
|
||||
slc = None
|
||||
if slice_id:
|
||||
slc = (
|
||||
db.session.query(models.Slice)
|
||||
.filter_by(id=request.args.get("slice_id"))
|
||||
.first()
|
||||
)
|
||||
if request.args.get("json") == "true":
|
||||
status = 200
|
||||
if config.get("DEBUG"):
|
||||
|
@ -451,9 +464,11 @@ class Panoramix(BaseView):
|
|||
mimetype="application/json")
|
||||
else:
|
||||
if config.get("DEBUG"):
|
||||
resp = self.render_template("panoramix/viz.html", viz=obj)
|
||||
resp = self.render_template(
|
||||
"panoramix/viz.html", viz=obj, slice=slc)
|
||||
try:
|
||||
resp = self.render_template("panoramix/viz.html", viz=obj)
|
||||
resp = self.render_template(
|
||||
"panoramix/viz.html", viz=obj, slice=slc)
|
||||
except Exception as e:
|
||||
if config.get("DEBUG"):
|
||||
raise(e)
|
||||
|
@ -490,6 +505,9 @@ class Panoramix(BaseView):
|
|||
dash = session.query(Dash).filter_by(id=dashboard_id).first()
|
||||
dash.slices = [o for o in dash.slices if o.id in slice_ids]
|
||||
dash.position_json = json.dumps(data['positions'], indent=4)
|
||||
dash.json_metadata = json.dumps({
|
||||
'expanded_slices': data['expanded_slices'],
|
||||
}, indent=4)
|
||||
dash.css = data['css']
|
||||
session.merge(dash)
|
||||
session.commit()
|
||||
|
|
Loading…
Reference in New Issue