Further refactoring around dashboards (#3843)

I was wondering what was left to do in order to remove Dashboard.jsx
and superset.js, and it looks like they can just be pulled out.

I am so happy to get rid of what used to be the messiest JS files in the
whole repo.

Thanks @graceguo!
This commit is contained in:
Maxime Beauchemin 2017-11-16 00:27:15 -08:00 committed by Grace Guo
parent 120a5d08f9
commit 1c545d3a2d
2 changed files with 0 additions and 643 deletions

View File

@ -1,381 +0,0 @@
import React from 'react';
import { render } from 'react-dom';
import d3 from 'd3';
import { Alert } from 'react-bootstrap';
import moment from 'moment';
import GridLayout from './components/GridLayout';
import Header from './components/Header';
import { appSetup } from '../common';
import AlertsWrapper from '../components/AlertsWrapper';
import { t } from '../locales';
import '../../stylesheets/dashboard.css';
const superset = require('../modules/superset');
const urlLib = require('url');
const utils = require('../modules/utils');
let px;
appSetup();
export function getInitialState(boostrapData) {
const dashboard = Object.assign(
{},
utils.controllerInterface,
boostrapData.dashboard_data,
{ common: boostrapData.common });
dashboard.firstLoad = true;
dashboard.posDict = {};
if (dashboard.position_json) {
dashboard.position_json.forEach((position) => {
dashboard.posDict[position.slice_id] = position;
});
}
dashboard.refreshTimer = null;
const state = Object.assign({}, boostrapData, { dashboard });
return state;
}
function unload() {
const message = t('You have unsaved changes.');
window.event.returnValue = message; // Gecko + IE
return message; // Gecko + Webkit, Safari, Chrome etc.
}
function onBeforeUnload(hasChanged) {
if (hasChanged) {
window.addEventListener('beforeunload', unload);
} else {
window.removeEventListener('beforeunload', unload);
}
}
function renderAlert() {
render(
<div className="container-fluid">
<Alert bsStyle="warning">
<strong>{t('You have unsaved changes.')}</strong> {t('Click the')} &nbsp;
<i className="fa fa-save" />&nbsp;
{t('button on the top right to save your changes.')}
</Alert>
</div>,
document.getElementById('alert-container'),
);
}
function initDashboardView(dashboard) {
render(
<div>
<AlertsWrapper initMessages={dashboard.common.flash_messages} />
<Header dashboard={dashboard} />
</div>,
document.getElementById('dashboard-header'),
);
// eslint-disable-next-line no-param-reassign
dashboard.reactGridLayout = render(
<GridLayout dashboard={dashboard} />,
document.getElementById('grid-container'),
);
// Displaying widget controls on hover
$('.react-grid-item').hover(
function () {
$(this).find('.chart-controls').fadeIn(300);
},
function () {
$(this).find('.chart-controls').fadeOut(300);
},
);
$('div.grid-container').css('visibility', 'visible');
$('div.widget').click(function (e) {
const $this = $(this);
const $target = $(e.target);
if ($target.hasClass('slice_info')) {
$this.find('.slice_description').slideToggle(0, function () {
$this.find('.refresh').click();
});
} else if ($target.hasClass('controls-toggle')) {
$this.find('.chart-controls').toggle();
}
});
px.initFavStars();
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
}
export function dashboardContainer(dashboard, datasources, userid) {
return Object.assign({}, dashboard, {
type: 'dashboard',
filters: {},
curUserId: userid,
init() {
this.sliceObjects = [];
dashboard.slices.forEach((data) => {
if (data.error) {
const html = `<div class="alert alert-danger">${data.error}</div>`;
$(`#slice_${data.slice_id}`).find('.token').html(html);
} else {
const slice = px.Slice(data, datasources[data.form_data.datasource], this);
$(`#slice_${data.slice_id}`).find('a.refresh').click(() => {
slice.render(true);
});
this.sliceObjects.push(slice);
}
});
this.loadPreSelectFilters();
this.renderSlices(this.sliceObjects);
this.firstLoad = false;
this.bindResizeToWindowResize();
},
onChange() {
onBeforeUnload(true);
renderAlert();
},
onSave() {
onBeforeUnload(false);
$('#alert-container').html('');
},
loadPreSelectFilters() {
try {
const filters = JSON.parse(px.getParam('preselect_filters') || '{}');
for (const sliceId in filters) {
for (const col in filters[sliceId]) {
this.setFilter(sliceId, col, filters[sliceId][col], false, false);
}
}
} catch (e) {
// console.error(e);
}
},
setFilter(sliceId, col, vals, refresh) {
this.addFilter(sliceId, col, vals, false, refresh);
},
done(slice) {
const refresh = slice.getWidgetHeader().find('.refresh');
const data = slice.data;
const cachedWhen = moment.utc(data.cached_dttm).fromNow();
if (data !== undefined && data.is_cached) {
refresh
.addClass('danger')
.attr(
'title',
t('Served from data cached %s . Click to force refresh.', cachedWhen))
.tooltip('fixTitle');
} else {
refresh
.removeClass('danger')
.attr('title', t('Click to force refresh'))
.tooltip('fixTitle');
}
},
effectiveExtraFilters(sliceId) {
const f = [];
const immuneSlices = this.metadata.filter_immune_slices || [];
if (sliceId && immuneSlices.includes(sliceId)) {
// The slice is immune to dashboard filters
return f;
}
// Building a list of fields the slice is immune to filters on
let immuneToFields = [];
if (
sliceId &&
this.metadata.filter_immune_slice_fields &&
this.metadata.filter_immune_slice_fields[sliceId]) {
immuneToFields = this.metadata.filter_immune_slice_fields[sliceId];
}
for (const filteringSliceId in this.filters) {
if (filteringSliceId === sliceId.toString()) {
// Filters applied by the slice don't apply to itself
continue;
}
for (const field in this.filters[filteringSliceId]) {
if (!immuneToFields.includes(field)) {
f.push({
col: field,
op: 'in',
val: this.filters[filteringSliceId][field],
});
}
}
}
return f;
},
addFilter(sliceId, col, vals, merge = true, refresh = true) {
if (
this.getSlice(sliceId) && (
['__from', '__to', '__time_col', '__time_grain', '__time_origin', '__granularity']
.indexOf(col) >= 0 ||
this.getSlice(sliceId).formData.groupby.indexOf(col) !== -1
)
) {
if (!(sliceId in this.filters)) {
this.filters[sliceId] = {};
}
if (!(col in this.filters[sliceId]) || !merge) {
this.filters[sliceId][col] = vals;
// d3.merge pass in array of arrays while some value form filter components
// from and to filter box require string to be process and return
} else if (this.filters[sliceId][col] instanceof Array) {
this.filters[sliceId][col] = d3.merge([this.filters[sliceId][col], vals]);
} else {
this.filters[sliceId][col] = d3.merge([[this.filters[sliceId][col]], vals])[0] || '';
}
if (refresh) {
this.refreshExcept(sliceId);
}
}
this.updateFilterParamsInUrl();
},
readFilters() {
// Returns a list of human readable active filters
return JSON.stringify(this.filters, null, ' ');
},
updateFilterParamsInUrl() {
const urlObj = urlLib.parse(location.href, true);
urlObj.query = urlObj.query || {};
urlObj.query.preselect_filters = this.readFilters();
urlObj.search = null;
history.pushState(urlObj.query, window.title, urlLib.format(urlObj));
},
bindResizeToWindowResize() {
let resizeTimer;
const dash = this;
$(window).on('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
dash.sliceObjects.forEach((slice) => {
slice.resize();
});
}, 500);
});
},
stopPeriodicRender() {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
},
renderSlices(slices, force = false, interval = 0) {
if (!interval) {
slices.forEach(slice => slice.render(force));
return;
}
const meta = this.metadata;
const refreshTime = Math.max(interval, meta.stagger_time || 5000); // default 5 seconds
if (typeof meta.stagger_refresh !== 'boolean') {
meta.stagger_refresh = meta.stagger_refresh === undefined ?
true : meta.stagger_refresh === 'true';
}
const delay = meta.stagger_refresh ? refreshTime / (slices.length - 1) : 0;
slices.forEach((slice, i) => {
setTimeout(() => slice.render(force), delay * i);
});
},
startPeriodicRender(interval) {
this.stopPeriodicRender();
const dash = this;
const immune = this.metadata.timed_refresh_immune_slices || [];
const refreshAll = () => {
const slices = dash.sliceObjects
.filter(slice => immune.indexOf(slice.data.slice_id) === -1);
dash.fetchSlices(slices, true, interval * 0.2);
};
const fetchAndRender = function () {
refreshAll();
if (interval > 0) {
dash.refreshTimer = setTimeout(function () {
fetchAndRender();
}, interval);
}
};
fetchAndRender();
},
refreshExcept(sliceId) {
const immune = this.metadata.filter_immune_slices || [];
const slices = this.sliceObjects.filter(slice =>
slice.data.slice_id !== sliceId && immune.indexOf(slice.data.slice_id) === -1);
this.renderSlices(slices);
},
clearFilters(sliceId) {
delete this.filters[sliceId];
this.refreshExcept(sliceId);
this.updateFilterParamsInUrl();
},
removeFilter(sliceId, col, vals) {
if (sliceId in this.filters) {
if (col in this.filters[sliceId]) {
const a = [];
this.filters[sliceId][col].forEach(function (v) {
if (vals.indexOf(v) < 0) {
a.push(v);
}
});
this.filters[sliceId][col] = a;
}
}
this.refreshExcept(sliceId);
this.updateFilterParamsInUrl();
},
getSlice(sliceId) {
const id = parseInt(sliceId, 10);
let i = 0;
let slice = null;
while (i < this.sliceObjects.length) {
// when the slice is found, assign to slice and break;
if (this.sliceObjects[i].data.slice_id === id) {
slice = this.sliceObjects[i];
break;
}
i++;
}
return slice;
},
getAjaxErrorMsg(error) {
const respJSON = error.responseJSON;
return (respJSON && respJSON.message) ? respJSON.message :
error.responseText;
},
addSlicesToDashboard(sliceIds) {
const getAjaxErrorMsg = this.getAjaxErrorMsg;
$.ajax({
type: 'POST',
url: `/superset/add_slices/${dashboard.id}/`,
data: {
data: JSON.stringify({ slice_ids: sliceIds }),
},
success() {
// Refresh page to allow for slices to re-render
window.location.reload();
},
error(error) {
const errorMsg = getAjaxErrorMsg(error);
utils.showModal({
title: t('Error'),
body: t('Sorry, there was an error adding slices to this dashboard: %s', errorMsg),
});
},
});
},
updateDashboardTitle(title) {
this.dashboard_title = title;
this.onChange();
},
});
}
$(document).ready(() => {
// Getting bootstrapped data from the DOM
utils.initJQueryAjax();
const dashboardData = $('.dashboard').data('bootstrap');
const state = getInitialState(dashboardData);
px = superset(state);
const dashboard = dashboardContainer(state.dashboard, state.datasources, state.userId);
initDashboardView(dashboard);
dashboard.init();
});

View File

@ -1,262 +0,0 @@
/* eslint camel-case: 0 */
import $ from 'jquery';
import Mustache from 'mustache';
import vizMap from '../../visualizations/main';
import { getExploreUrl } from '../explore/exploreUtils';
import { applyDefaultFormData } from '../explore/stores/store';
import { t } from '../locales';
const utils = require('./utils');
/* eslint wrap-iife: 0 */
const px = function (state) {
let slice;
const timeout = state.common.conf.SUPERSET_WEBSERVER_TIMEOUT;
function getParam(name) {
/* eslint no-useless-escape: 0 */
const formattedName = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
const regex = new RegExp('[\\?&]' + formattedName + '=([^&#]*)');
const results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
function initFavStars() {
const baseUrl = '/superset/favstar/';
// Init star behavihor for favorite
function show() {
if ($(this).hasClass('selected')) {
$(this).html('<i class="fa fa-star"></i>');
} else {
$(this).html('<i class="fa fa-star-o"></i>');
}
}
$('.favstar')
.attr('title', t('Click to favorite/unfavorite'))
.css('cursor', 'pointer')
.each(show)
.each(function () {
let url = baseUrl + $(this).attr('class_name');
const star = this;
url += '/' + $(this).attr('obj_id') + '/';
$.getJSON(url + 'count/', function (data) {
if (data.count > 0) {
$(star).addClass('selected').each(show);
}
});
})
.click(function () {
$(this).toggleClass('selected');
let url = baseUrl + $(this).attr('class_name');
url += '/' + $(this).attr('obj_id') + '/';
if ($(this).hasClass('selected')) {
url += 'select/';
} else {
url += 'unselect/';
}
$.get(url);
$(this).each(show);
})
.tooltip();
}
const Slice = function (data, datasource, controller) {
const token = $('#token_' + data.slice_id);
const controls = $('#controls_' + data.slice_id);
const containerId = 'con_' + data.slice_id;
const selector = '#' + containerId;
const container = $(selector);
const sliceId = data.slice_id;
const formData = applyDefaultFormData(data.form_data);
const sliceCell = $(`#${data.slice_id}-cell`);
slice = {
data,
formData,
container,
containerId,
datasource,
selector,
getWidgetHeader() {
return this.container.parents('div.widget').find('.chart-header');
},
render_template(s) {
const context = {
width: this.width,
height: this.height,
};
return Mustache.render(s, context);
},
jsonEndpoint(data) {
return this.endpoint(data, 'json');
},
endpoint(data, endpointType = 'json') {
let endpoint = getExploreUrl(data, endpointType, this.force);
if (endpoint.charAt(0) !== '/') {
// Known issue for IE <= 11:
// https://connect.microsoft.com/IE/feedbackdetail/view/1002846/pathname-incorrect-for-out-of-document-elements
endpoint = '/' + endpoint;
}
return endpoint;
},
d3format(col, number) {
// uses the utils memoized d3format function and formats based on
// column level defined preferences
let format = '.3s';
if (this.datasource.column_formats[col]) {
format = this.datasource.column_formats[col];
}
return utils.d3format(format, number);
},
/* eslint no-shadow: 0 */
always(data) {
if (data && data.query) {
slice.viewSqlQuery = data.query;
}
},
done(payload) {
Object.assign(data, payload);
token.find('img.loading').hide();
container.fadeTo(0.5, 1);
sliceCell.removeClass('slice-cell-highlight');
container.show();
$('.query-and-save button').removeAttr('disabled');
this.always(data);
controller.done(this);
},
getErrorMsg(xhr) {
let msg = '';
if (!xhr.responseText) {
const status = xhr.status;
if (status === 0) {
// This may happen when the worker in gunicorn times out
msg += (
t('The server could not be reached. You may want to ' +
'verify your connection and try again.'));
} else {
msg += (t('An unknown error occurred. (Status: %s )', status));
}
}
return msg;
},
error(msg, xhr) {
let errorMsg = msg;
token.find('img.loading').hide();
container.fadeTo(0.5, 1);
sliceCell.removeClass('slice-cell-highlight');
let errHtml = '';
let o;
try {
o = JSON.parse(msg);
if (o.error) {
errorMsg = o.error;
}
} catch (e) {
// pass
}
if (errorMsg) {
errHtml += `<div class="alert alert-danger">${errorMsg}</div>`;
}
if (xhr) {
if (xhr.statusText === 'timeout') {
errHtml += (
'<div class="alert alert-warning">' +
'Query timeout - visualization query are set to time out ' +
`at ${timeout} seconds.</div>`);
} else {
const extendedMsg = this.getErrorMsg(xhr);
if (extendedMsg) {
errHtml += `<div class="alert alert-danger">${extendedMsg}</div>`;
}
}
}
container.html(errHtml);
container.show();
$('span.query').removeClass('disabled');
$('.query-and-save button').removeAttr('disabled');
this.always(o);
controller.error(this);
},
clearError() {
$(selector + ' div.alert').remove();
},
width() {
return container.width();
},
height() {
let others = 0;
const widget = container.parents('.widget');
const sliceDescription = widget.find('.slice_description');
if (sliceDescription.is(':visible')) {
others += widget.find('.slice_description').height() + 25;
}
others += widget.find('.chart-header').height();
return widget.height() - others - 10;
},
bindResizeToWindowResize() {
let resizeTimer;
const slice = this;
$(window).on('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
slice.resize();
}, 500);
});
},
render(force) {
if (force === undefined) {
this.force = false;
} else {
this.force = force;
}
const formDataExtra = Object.assign({}, formData);
formDataExtra.extra_filters = controller.effectiveExtraFilters(sliceId);
controls.find('a.exploreChart').attr('href', getExploreUrl(formDataExtra));
controls.find('a.exportCSV').attr('href', getExploreUrl(formDataExtra, 'csv'));
token.find('img.loading').show();
container.fadeTo(0.5, 0.25);
sliceCell.addClass('slice-cell-highlight');
container.css('height', this.height());
$.ajax({
url: this.jsonEndpoint(formDataExtra),
timeout: timeout * 1000,
success: (queryResponse) => {
try {
vizMap[formData.viz_type](this, queryResponse);
this.done(queryResponse);
} catch (e) {
this.error(t('An error occurred while rendering the visualization: %s', e));
}
},
error: (err) => {
this.error(err.responseText, err);
},
});
},
resize() {
this.render();
},
addFilter(col, vals, merge = true, refresh = true) {
controller.addFilter(sliceId, col, vals, merge, refresh);
},
setFilter(col, vals, refresh = true) {
controller.setFilter(sliceId, col, vals, refresh);
},
getFilters() {
return controller.filters[sliceId];
},
clearFilter() {
controller.clearFilter(sliceId);
},
removeFilter(col, vals) {
controller.removeFilter(sliceId, col, vals);
},
};
return slice;
};
// Export public functions
return {
getParam,
initFavStars,
Slice,
};
};
module.exports = px;