Skip to content

Commit

Permalink
"Add Slices" modal on dashboard page (#678)
Browse files Browse the repository at this point in the history
* Add slice modal

* use datatables, filter by slice creator

* tests & landscaping

* code review + react-bootstrap-table + modularity
  • Loading branch information
georgeke committed Jul 8, 2016
1 parent afff788 commit 04f3e3b
Show file tree
Hide file tree
Showing 10 changed files with 600 additions and 252 deletions.
Original file line number Diff line number Diff line change
@@ -1,196 +1,22 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var px = require('./modules/caravel.js');
var px = require('../modules/caravel.js');
var d3 = require('d3');
var showModal = require('./modules/utils.js').showModal;
require('bootstrap');
var showModal = require('../modules/utils.js').showModal;

import React from 'react';
import { render } from 'react-dom';
import SliceAdder from './components/SliceAdder.jsx';
import GridLayout from './components/GridLayout.jsx';

var ace = require('brace');
require('bootstrap');
require('brace/mode/css');
require('brace/theme/crimson_editor');

require('./caravel-select2.js');
require('../node_modules/react-grid-layout/css/styles.css');
require('../node_modules/react-resizable/css/styles.css');

require('../stylesheets/dashboard.css');

import { Responsive, WidthProvider } from "react-grid-layout";
const ResponsiveReactGridLayout = WidthProvider(Responsive);

class SliceCell extends React.Component {
render() {
const slice = this.props.slice,
createMarkup = function () {
return { __html: slice.description_markeddown };
};

return (
<div>
<div className="chart-header">
<div className="row">
<div className="col-md-12 text-center header">
{slice.slice_name}
</div>
<div className="col-md-12 chart-controls">
<div className="pull-left">
<a title="Move chart" data-toggle="tooltip">
<i className="fa fa-arrows drag"/>
</a>
<a className="refresh" title="Force refresh data" data-toggle="tooltip">
<i className="fa fa-repeat"/>
</a>
</div>
<div className="pull-right">
{slice.description ?
<a title="Toggle chart description">
<i className="fa fa-info-circle slice_info" title={slice.description} data-toggle="tooltip"/>
</a>
: ""}
<a href={slice.edit_url} title="Edit chart" data-toggle="tooltip">
<i className="fa fa-pencil"/>
</a>
<a href={slice.slice_url} title="Explore chart" data-toggle="tooltip">
<i className="fa fa-share"/>
</a>
<a className="remove-chart" title="Remove chart from dashboard" data-toggle="tooltip">
<i className="fa fa-close" onClick={this.props.removeSlice.bind(null, slice.slice_id)}/>
</a>
</div>
</div>

</div>
</div>
<div
className="slice_description bs-callout bs-callout-default"
style={this.props.expandedSlices && this.props.expandedSlices[String(slice.slice_id)] ? {} : { display: "none" }}
dangerouslySetInnerHTML={createMarkup()}>
</div>
<div className="row chart-container">
<input type="hidden" value="false"/>
<div id={slice.token} className="token col-md-12">
<img src={"/static/assets/images/loading.gif"} className="loading" alt="loading"/>
<div className="slice_container" id={slice.token + "_con"}></div>
</div>
</div>
</div>
);
}
}

class GridLayout extends React.Component {
removeSlice(sliceId) {
$('[data-toggle="tooltip"]').tooltip("hide");
this.setState({
layout: this.state.layout.filter(function (reactPos) {
return reactPos.i !== String(sliceId);
}),
slices: this.state.slices.filter(function (slice) {
return slice.slice_id !== sliceId;
}),
sliceElements: this.state.sliceElements.filter(function (sliceElement) {
return sliceElement.key !== String(sliceId);
})
});
}

onResizeStop(layout, oldItem, newItem) {
if (oldItem.w != newItem.w || oldItem.h != newItem.h) {
this.setState({
layout: layout
}, function () {
this.props.dashboard.getSlice(newItem.i).resize();
});
}
}

onDragStop(layout) {
this.setState({
layout: layout
});
}

serialize() {
return this.state.layout.map(function (reactPos) {
return {
slice_id: reactPos.i,
col: reactPos.x + 1,
row: reactPos.y,
size_x: reactPos.w,
size_y: reactPos.h
};
});
}

componentWillMount() {
var layout = [],
sliceElements = [];

this.props.slices.forEach(function (slice, index) {
var pos = this.props.posDict[slice.slice_id];
if (!pos) {
pos = {
col: (index * 4 + 1) % 12,
row: Math.floor((index) / 3) * 4,
size_x: 4,
size_y: 4
};
}

sliceElements.push(
<div
id={"slice_" + slice.slice_id}
key={slice.slice_id}
data-slice-id={slice.slice_id}
className={"widget " + slice.viz_name}>
<SliceCell
slice={slice}
removeSlice={this.removeSlice.bind(this)}
expandedSlices={this.props.dashboard.metadata.expanded_slices}/>
</div>
);

layout.push({
i: String(slice.slice_id),
x: pos.col - 1,
y: pos.row,
w: pos.size_x,
minW: 2,
h: pos.size_y
});
}, this);

this.setState({
layout: layout,
sliceElements: sliceElements,
slices: this.props.slices
});
}

render() {
return (
<ResponsiveReactGridLayout
className="layout"
layouts={{ lg: this.state.layout }}
onResizeStop={this.onResizeStop.bind(this)}
onDragStop={this.onDragStop.bind(this)}
cols={{ lg: 12, md: 12, sm: 10, xs: 8, xxs: 6 }}
rowHeight={100}
autoSize={true}
margin={[20, 20]}
useCSSTransforms={false}
draggableHandle=".drag">
{this.state.sliceElements}
</ResponsiveReactGridLayout>
);
}
}
require('../caravel-select2.js');
require('../../stylesheets/dashboard.css');

var Dashboard = function (dashboardData) {
var reactGridLayout;

var dashboard = $.extend(dashboardData, {
filters: {},
init: function () {
Expand All @@ -202,10 +28,10 @@ var Dashboard = function (dashboardData) {
dashboard.slices.forEach(function (data) {
if (data.error) {
var html = '<div class="alert alert-danger">' + data.error + '</div>';
$("#slice_" + data.slice_id).find('.token').html(html);
$('#slice_' + data.slice_id).find('.token').html(html);
} else {
var slice = px.Slice(data, dash);
$("#slice_" + data.slice_id).find('a.refresh').click(function () {
$('#slice_' + data.slice_id).find('a.refresh').click(function () {
slice.render(true);
});
sliceObjects.push(slice);
Expand Down Expand Up @@ -315,17 +141,90 @@ var Dashboard = function (dashboardData) {
}
}
},
showAddSlice: function () {
var slicesOnDashMap = {};
this.reactGridLayout.serialize().forEach(function (position) {
slicesOnDashMap[position.slice_id] = true;
}, this);

render(
<SliceAdder dashboard={dashboard} slicesOnDashMap={slicesOnDashMap} caravel={px} />,
document.getElementById("add-slice-container")
);
},
getAjaxErrorMsg: function (error) {
var respJSON = error.responseJSON;
return (respJSON && respJSON.message) ? respJSON.message :
error.responseText;
},
addSlicesToDashboard: function (sliceIds) {
$.ajax({
type: "POST",
url: '/caravel/add_slices/' + dashboard.id + '/',
data: {
data: JSON.stringify({ slice_ids: sliceIds })
},
success: function () {
// Refresh page to allow for slices to re-render
window.location.reload();
},
error: function (error) {
var errorMsg = this.getAjaxErrorMsg(error);
showModal({
title: "Error",
body: "Sorry, there was an error adding slices to this dashboard: </ br>" + errorMsg
});
}.bind(this)
});
},
saveDashboard: function () {
var expandedSlices = {};
$.each($(".slice_info"), function (i, d) {
var widget = $(this).parents('.widget');
var sliceDescription = widget.find('.slice_description');
if (sliceDescription.is(":visible")) {
expandedSlices[$(widget).attr('data-slice-id')] = true;
}
});
var data = {
positions: this.reactGridLayout.serialize(),
css: this.editor.getValue(),
expanded_slices: expandedSlices
};
$.ajax({
type: "POST",
url: '/caravel/save_dash/' + dashboard.id + '/',
data: {
data: JSON.stringify(data)
},
success: function () {
showModal({
title: "Success",
body: "This dashboard was saved successfully."
});
},
error: function (error) {
var errorMsg = this.getAjaxErrorMsg(error);
showModal({
title: "Error",
body: "Sorry, there was an error saving this dashboard: </ br>" + errorMsg
});
}
});
},
initDashboardView: function () {
var posDict = {}
this.posDict = {};
this.position_json.forEach(function (position) {
posDict[position.slice_id] = position;
});
this.posDict[position.slice_id] = position;
}, this);

reactGridLayout = render(
<GridLayout slices={this.slices} posDict={posDict} dashboard={dashboard}/>,
this.reactGridLayout = render(
<GridLayout slices={this.slices} posDict={this.posDict} dashboard={dashboard}/>,
document.getElementById("grid-container")
);

this.curUserId = $('.dashboard').data('user');

dashboard = this;

// Displaying widget controls on hover
Expand All @@ -338,46 +237,11 @@ var Dashboard = function (dashboardData) {
}
);
$("div.grid-container").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[$(widget).attr('data-slice-id')] = true;
}
});
var data = {
positions: reactGridLayout.serialize(),
css: editor.getValue(),
expanded_slices: expanded_slices
};
$.ajax({
type: "POST",
url: '/caravel/save_dash/' + dashboard.id + '/',
data: {
data: JSON.stringify(data)
},
success: function () {
showModal({
title: "Success",
body: "This dashboard was saved successfully."
});
},
error: function (error) {
var respJSON = error.responseJSON;
var errorMsg = (respJSON && respJSON.message) ? respJSON.message :
error.responseText;
showModal({
title: "Error",
body: "Sorry, there was an error saving this dashboard:<br />" + errorMsg
});
console.warn("Save dashboard error", error);
}
});
});
$("#savedash").click(this.saveDashboard.bind(this));
$("#add-slice").click(this.showAddSlice.bind(this));

var editor = ace.edit("dash_css");
this.editor = editor;
editor.$blockScrolling = Infinity;

editor.setTheme("ace/theme/crimson_editor");
Expand Down
Loading

0 comments on commit 04f3e3b

Please sign in to comment.