diff --git a/docs/faq.rst b/docs/faq.rst index 82280ed46bdfe..21e23fcdde4c8 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -143,6 +143,24 @@ To exclude specific slices from the timed refresh process, add the ``timed_refre In the example above, if a timed refresh is set for the dashboard, then every slice except 324 will be automatically requeried on schedule. +How to speed up/slow down dashboard loading? +-------------------------------------------- +Dashboards stagger slice rendering by default over a period of 5 seconds. This reduces loads on +some databases but slows down loading time. You can disable render staggering by setting the key +``stagger_refresh`` in the ``JSON Metadata`` to ``false`` or alter the stagger period by setting +``stagger_time`` to a value in milliseconds: + +..code:: + + { + "stagger_refresh": false, + "stagger_time": 2000 + } + +Here, whenever the dashboard refreshes slices, all requests will be issued simultaneously. The +stagger time of 2 seconds is ignored. + + Why does fabmanager or superset freezed/hung/not responding when started (my home directory is NFS mounted)? ----------------------------------------------------------------------------------------- By default, superset creates and uses an sqlite database at ``~/.superset/superset.db``. Sqlite is known to `don't work well if used on NFS`__ due to broken file locking implementation on NFS. @@ -188,7 +206,7 @@ Please note that pretty much any databases that have a SqlAlchemy integration sh How can i configure OAuth authentication and authorization? ----------------------------------------------------------- -You can take a look at this Flask-AppBuilder `configuration example +You can take a look at this Flask-AppBuilder `configuration example `_. How can I set a default filter on my dashboard? diff --git a/superset/assets/javascripts/dashboard/Dashboard.jsx b/superset/assets/javascripts/dashboard/Dashboard.jsx index 8a7000ea8b3fc..6e07b983b353c 100644 --- a/superset/assets/javascripts/dashboard/Dashboard.jsx +++ b/superset/assets/javascripts/dashboard/Dashboard.jsx @@ -255,25 +255,35 @@ export function dashboardContainer(dashboard, datasources, userid) { this.refreshTimer = null; } }, + renderSlices(slices, interval = 0) { + const meta = this.metadata; + const refreshTime = Math.max(interval, meta.stagger_time || 5000); + // Delay is zero if not staggerring slice refresh + 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) => { + // Caller may pass array of slices or { slice, force } + if (slice.slice) { + slice.slice.render(slice.force, delay * i); + } else { + slice.render(false, delay * i); + } + }); + }, startPeriodicRender(interval) { this.stopPeriodicRender(); const dash = this; const immune = this.metadata.timed_refresh_immune_slices || []; - const maxRandomDelay = Math.max(interval * 0.2, 5000); const refreshAll = () => { - dash.sliceObjects.forEach((slice) => { - const force = !dash.firstLoad; - if (immune.indexOf(slice.data.slice_id) === -1) { - setTimeout(() => { - slice.render(force); - }, - // Randomize to prevent all widgets refreshing at the same time - maxRandomDelay * Math.random()); - } - }); + const slices = dash.sliceObjects + .filter(slice => immune.indexOf(slice.data.slice_id) === -1 || dash.firstLoad) + .map(slice => ({ slice, force: !dash.firstLoad })); dash.firstLoad = false; + dash.renderSlices(slices, interval * 0.2); }; - const fetchAndRender = function () { refreshAll(); if (interval > 0) { @@ -286,16 +296,9 @@ export function dashboardContainer(dashboard, datasources, userid) { }, refreshExcept(sliceId) { const immune = this.metadata.filter_immune_slices || []; - this.sliceObjects.forEach((slice) => { - if (slice.data.slice_id !== sliceId && immune.indexOf(slice.data.slice_id) === -1) { - slice.render(); - const sliceSeletor = $(`#${slice.data.slice_id}-cell`); - sliceSeletor.addClass('slice-cell-highlight'); - setTimeout(function () { - sliceSeletor.removeClass('slice-cell-highlight'); - }, 1200); - } - }); + 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]; diff --git a/superset/assets/javascripts/dashboard/components/Controls.jsx b/superset/assets/javascripts/dashboard/components/Controls.jsx index 1169642ff60e5..fd7689799a37d 100644 --- a/superset/assets/javascripts/dashboard/components/Controls.jsx +++ b/superset/assets/javascripts/dashboard/components/Controls.jsx @@ -34,9 +34,8 @@ class Controls extends React.PureComponent { }); } refresh() { - this.props.dashboard.sliceObjects.forEach((slice) => { - slice.render(true); - }); + const slices = this.props.dashboard.sliceObjects.map(slice => ({ slice, force: true })); + this.props.dashboard.renderSlices(slices); } changeCss(css) { this.setState({ css }); diff --git a/superset/assets/javascripts/modules/superset.js b/superset/assets/javascripts/modules/superset.js index aaf1e85573559..641826252851d 100644 --- a/superset/assets/javascripts/modules/superset.js +++ b/superset/assets/javascripts/modules/superset.js @@ -64,6 +64,7 @@ const px = function (state) { const container = $(selector); const sliceId = data.slice_id; const formData = applyDefaultFormData(data.form_data); + const sliceCell = $(`#${data.slice_id}-cell`); slice = { data, formData, @@ -113,6 +114,7 @@ const px = function (state) { token.find('img.loading').hide(); container.fadeTo(0.5, 1); + sliceCell.removeClass('slice-cell-highlight'); container.show(); $('.query-and-save button').removeAttr('disabled'); @@ -138,6 +140,7 @@ const px = function (state) { let errorMsg = msg; token.find('img.loading').hide(); container.fadeTo(0.5, 1); + sliceCell.removeClass('slice-cell-highlight'); let errHtml = ''; let o; try { @@ -197,7 +200,7 @@ const px = function (state) { }, 500); }); }, - render(force) { + render(force, delay = 0) { if (force === undefined) { this.force = false; } else { @@ -210,22 +213,25 @@ const px = function (state) { 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('An error occurred while rendering the visualization: ' + e); - } - }, - error: (err) => { - this.error(err.responseText, err); - }, - }); + setTimeout(() => { + $.ajax({ + url: this.jsonEndpoint(formDataExtra), + timeout: timeout * 1000, + success: (queryResponse) => { + try { + vizMap[formData.viz_type](this, queryResponse); + this.done(queryResponse); + } catch (e) { + this.error('An error occurred while rendering the visualization: ' + e); + } + }, + error: (err) => { + this.error(err.responseText, err); + }, + }); + }, delay); }, resize() { this.render();