mirror of https://github.com/apache/superset.git
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:
parent
120a5d08f9
commit
1c545d3a2d
|
@ -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')}
|
|
||||||
<i className="fa fa-save" />
|
|
||||||
{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();
|
|
||||||
});
|
|
|
@ -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;
|
|
Loading…
Reference in New Issue