How do I build a real-time dashboard that updates graph-like UIs every second?

Goal:
Realtime data steaming display. The expected behavior is to be able to connect to PusherJS to receive realtime data update from server.

Steps:

Seems like the only way is to use custom component + WebSocket. We are using PusherJS, the current workaround would require extra configuration and setup to manage the data and re-pipe the data to WebSocket.

Is long polling not an option? You could have a query run every second and pull fresh data into the chart?

1 Like

Not really. Our system use Pusher to broadcast realtime data. Due to the way our system operate, we can't use pull. We are also not storing the realtime data.

Hi @boondoge,

We currently do not have any built in integrations for this type of functionality.

You may be able to hack together something using @khill-fbmc's suggestion of running a query to a REST API and have that server serve the data back to the app.

We have docs on data streaming here.

Also a workflow has an exposed trigger URL that could be the recipient of data payloads from a webhook. This could be used to get data and move it to a place where it is accessible by an app query.

Hey all! I still think that a custom component will be your best option here, but it doesn't need to be a super low-level solution. Is there any reason not to build out a pusher-js client that can hook directly into the data stream from your server? I don't know exactly how the library is built, but I have to imagine that it utilizes websockets.

Hey @boondoge and @lchen110 - have you had a chance to review this project and some of the feedback provided above?

I agree here, PusherJS does use websockets so a custom component would work. It wouldn't need any UI parts, it just needs to emit events for your frontend to know when to update:


// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const auditQuestions: FC = ({ children }: any) => {
    const onConnectionError = Retool.useEventCallback({
        name: 'onConnectionError'
    })

    const onConnection = Retool.useEventCallback({
        name: 'onConnection'
    })

    const onDisconnection = Retool.useEventCallback({
        name: 'onDisconnection'
    })

    const onStateChange = Retool.useEventCallback({
        name: 'onStateChange'
    })

    const onUnknownEvent = Retool.useEventCallback({
        name: 'onUnknownEvent'
    })

    const onNewMessageEvent = Retool.useEventCallback({
        name: 'onNewMessageEvent'
    })

    const onMyEvent = Retool.useEventCallback({
        name: 'onMyEvent'
    })

    const pusher = useRef(new Pusher('app_key', { cluster: APP_CLUSTER }));

    useEffect(() => {
        pusher.current.connection.bind_global(function (event, data) {
            switch(event){
                case 'connected':
                    console.log("Connected to Pusher");
                    onConnection();
                    break;
                case 'disconnected':
                    console.log("Disconnected from Pusher");
                    onDisconnection();
                    break;
                case 'error':
                    if(data.code === 4004) {
                        onConnectionError();
                    }
                    break;
                case 'state_change':
                    console.log("State changed from " + data.previous + " to " + data.current);
                    onStateChange();
                    break;
                default:
                    console.log("Unknown Connection Event: ", event);
                    console.log("Event Data: ", data);
                    onUnknownEvent();
            }
        })

        pusher.current.subscribe('my-channel');
        pusher.current.subscribe('another-channel');
        pusher.current.subscribe('private-channel');

        pusher.allChannels().forEach(channel =>
            channel.bind_global(function (event, data) {
                console.log("Channel: ", channel)
                switch(event){
                    case 'my-event':
                        console.log("Event: ", event)
                        console.log("Event Data: ", data)
                        onMyEvent();
                        break;
                    case 'new-message':
                        console.log("Event: ", event)
                        onNewMessageEvent();
                        break;
                    default:                 
                        console.log("Unknown Channel Event: ", event)
                        console.log("Event Data: ", data)
                        onUnknownEvent();
                        break;
                }
            }) 
        )

 
    }, []);
}
1 Like