diff --git a/superset/assets/javascripts/dashboard/Dashboard.jsx b/superset/assets/javascripts/dashboard/Dashboard.jsx deleted file mode 100644 index 9e676472589..00000000000 --- a/superset/assets/javascripts/dashboard/Dashboard.jsx +++ /dev/null @@ -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( -
- - {t('You have unsaved changes.')} {t('Click the')}   -   - {t('button on the top right to save your changes.')} - -
, - document.getElementById('alert-container'), - ); -} - -function initDashboardView(dashboard) { - render( -
- -
-
, - document.getElementById('dashboard-header'), - ); - // eslint-disable-next-line no-param-reassign - dashboard.reactGridLayout = render( - , - 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 = `
${data.error}
`; - $(`#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(); -}); diff --git a/superset/assets/javascripts/modules/superset.js b/superset/assets/javascripts/modules/superset.js deleted file mode 100644 index 9d58f02e949..00000000000 --- a/superset/assets/javascripts/modules/superset.js +++ /dev/null @@ -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(''); - } else { - $(this).html(''); - } - } - $('.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 += `
${errorMsg}
`; - } - if (xhr) { - if (xhr.statusText === 'timeout') { - errHtml += ( - '
' + - 'Query timeout - visualization query are set to time out ' + - `at ${timeout} seconds.
`); - } else { - const extendedMsg = this.getErrorMsg(xhr); - if (extendedMsg) { - errHtml += `
${extendedMsg}
`; - } - } - } - 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;