Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTML component showing running status #90

Closed
RLstat opened this issue Jul 18, 2017 · 6 comments
Closed

HTML component showing running status #90

RLstat opened this issue Jul 18, 2017 · 6 comments

Comments

@RLstat
Copy link

RLstat commented Jul 18, 2017

Hi, dash users and developers,

I have a program that takes some time to run upon update with callback. I want to have a html component showing it is "Running" or "Completed".

I was trying to do as following:

@app.callback(
    dash.dependencies.Output('Output', 'value'),
    [dash.dependencies.Input('Input', 'value')])
def slow_function(input_value):
  (some code running slow)
   return(output_value)

@app.callback(
    dash.dependencies.Output('update-indicator', 'children'),
    [dash.dependencies.Input('Input', 'value')])
def show_running(input_value):
   return("Running")

@app.callback(
    dash.dependencies.Output('update-indicator', 'children'),
    [dash.dependencies.Input('Output', 'value')])
def show_running(output_value):
   return("Completed")

However, it suffers from two problems:

  1. Currently one object ("output") can only have one callback. so it will give an error.
  2. Even if point 1 is not a problem, in cases where different inputs result in the same output, I guess it will still showing "Running" even if a different run has happened and completed. Am I correct?

Please help me out either with the two problems or the original goal.

Thanks!!

@chriddyp
Copy link
Member

Great question @RLstat . There isn't a great way to do this with Input and Output components. I think that we need to add a first-class "Loading State" support to Dash that provides a configurable interface to adding loading styles or content to input and output elements or to the entire app itself. Some more thoughts about this here: https://community.plot.ly/t/progress-indicator-spinning-wheel-gif-for-long-running-callbacks/4946/2

@ned2
Copy link
Contributor

ned2 commented Jul 21, 2017

A progress spinner component for the entire app and/or individual elements would be great. I was just starting to think about the need for precisely this in the app I'm building.

@nirvana-msu
Copy link

I had the same problem and came up with the following workaround. There's a very sleek unobtrusive JavaScript progress bar rstacruz/nprogress, which is also extremely easy to use - you just need to call NProgress.start() and NProgress.done(). What's slightly tricky is that you need to hook into the events that are fired before and after the updates. With ajax that would've been down to simply registering ajaxStart and ajaxStop callbacks, however Dash uses Fetch API instead of XHR. There's a nice project werk85/fetch-intercept which money-patches fetch to allow us hooking into its events. This is an npm module, but it's simple enough to transform it into plain JavaScript so it could be included directly as a script. If you've got a modern browser supporting Fetch API, you could simply use this. In order to support older browsers though, we need to get rid of spread operator. Additionally, when registered through Dash we have no control over the order out scripts are executed relative to the Dash frontend scripts. In my case my script was loaded sooner, so fetch polyfill was not yet available - as a result I needed defer execution of that code until polyfill becomes available. Resulting script that supports old browsers is here.

Putting all of it together, I ended up with the following:

# Only used for $(document).ready callback - you can replace with plain JavaScript if you prefer
app.scripts.append_script({"external_url": "http://code.jquery.com/jquery-3.2.1.min.js"})

# NProgress is hosted on CDN
app.scripts.append_script({"external_url": "https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js"})
app.css.append_css({"external_url": "https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css"})

# This can be replaced with a simpler version mentioned above if you don't need to support old browsers
app.scripts.append_script({"external_url": "https://jsfiddle.net/nirvana_msu/bov2y1o0/show_js/"})

Finally, we need to register the following JavaScript. You can check this thread for advice on how to publish local assets.

$(document).ready(function () {
    // We hook into Fetch API events using monkey-patched version of fetch
    fetchIntercept.register({
        request: function (url, config) {
            NProgress.start();
            return [url, config];
        },

        response: function (response) {
            NProgress.done();
            return response
        }
    });
});

It works quite well, but still far from perfect. Since we're hooking into fetch response event, the loading indicator completes when the data is returned from server side. Instead, it should complete when UI actually finishes updating all the charts, which can take some time if your charts are complex. So ideally we need Dash to provide us with events such as when callback starts, and when rendering ends. If these events existed, we'd simply hook our loader to them and wouldn't have to bother monkey-patching Fetch API ...

@nirvana-msu
Copy link

And a bonus - you can give feedback to a user if there's been an error processing the update by changing progress bar color to red, making it full width, and keeping on screen until the next request fires. I'm using jQuery, but you can certainly do it without.

Javascript:

// Need to track number of active requests to update progress when last one finishes
var fetchRequestCount = 0;

$(document).ready(function () {
    // We hook into Fetch API events using monkey-patched version of fetch
    fetchIntercept.register({
        request: function (url, config) {
            fetchRequestCount++;
            // Reset progress bar in case there was an error previously
            $('html').removeClass('nprogress-error');
            NProgress.done();
            NProgress.start();

            return [url, config];
        },

        response: function (response) {
            fetchRequestCount--;

            if (fetchRequestCount == 0) {   // don't update progress bar unless last request finished loading
                if (!response.ok) {
                    $('html').addClass('nprogress-error'); // Highlight the fact there's been an error
                    NProgress.set(0.999);   // keep the bar visible, full length (indicating request has finished)
                } else {
                    NProgress.done();
                }
            }

            return response
        }
    });
});

Css:

.nprogress-error #nprogress .spinner {
    display: none !important;
}

.nprogress-error #nprogress .bar {
    background: red !important;
}

@chriddyp
Copy link
Member

chriddyp commented Oct 4, 2017

Some updates for loading state here: https://community.plot.ly/t/mega-dash-loading-states/5687

@chriddyp
Copy link
Member

chriddyp commented Jun 8, 2018

Closing in favor of #267, a proposal for adding loading states to the underlying component framework.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants