DEV Community

James Freund
James Freund

Posted on

Next Experience: Record Watchers

Precursor

Record watchers have been around for quite some time to provide real-time data on back-end forms in ServiceNow as well as in the Service Portal. Changes in instance data trigger an event on the front end, upon receiving the event you can then perform logic based on the change to instance data.

Similarly to making REST API calls there is a predefined npm package you can install to work just like @servicenow/ui-effect-http. This package is named @servicenow/ui-effect-amb and you will have to install it to utilize record watching functionality.

Record watchers can not truly be tested until the components are deployed to the instance directly. This is because there is no way to use the proxy from now-cli.json in websockets which is the paradigm that record watchers use.

This can make debugging quite difficult, so I apologize for the inconvenience.

Preparation

First things first you need to install the package.

npm install @servicenow/ui-effect-amb
Enter fullscreen mode Exit fullscreen mode

This package needs to be imported to create an actionHandler similar to when you import @servicenow/ui-effect-http.

import { createAmbSubscriptionEffect } from "@servicenow/ui-effect-amb";
Enter fullscreen mode Exit fullscreen mode

The function createAmbSubscriptionEffect takes two main parameters, a channelid string and an options object. Below is the definition of the effect and explains which parameters it expects.

createAmbSubscriptionEffect

 /*
 * @param {String} channelId - AMB Channel ID
 * @param {Object} options
 * @param {String} options.subscribeStartedActionType - Action dispatched when an amb subscription is started
 * @param {String} options.subscribeSucceededActionType - Action dispatched if an amb subscription succeeds
 * @param {String} options.subscribeFailedActionType - Action dispatched if an amb subscription fails
 * @param {String} options.unsubscribeSucceededActionType - Action dispatched when an amb unsubscribe is successful
 * @param {String} options.messageReceivedActionType - Action dispatched when a message is received on a channel
 * @param {Boolean} options.encodeURIComponentForChannelId - Encode channel id URL (default: true)
 *
 * @return {Object} AMB Subscription Effect Descriptor
 */
Enter fullscreen mode Exit fullscreen mode

Channel ID

The channel ID is almost always going to be the same string because it essentially serves as the endpoint to kick off the record watcher.

//! This string will almost always be:
const channelID = "/rw/default/:table/:filter";
Enter fullscreen mode Exit fullscreen mode

Usage

This function will return an object that can be attached directly to an actionHandler. The action handler that it is attached to will serve as the main way to start and stop a record watcher.

To make life easier I created a helper function which generates an ambSubscriptionEffect and fills in some default settings. Generally you will only need to really use the subscribeStartedActionType and messageReceivedActionType properties in the options object.

ambEffectHelper.js

import { createAmbSubscriptionEffect } from "@servicenow/ui-effect-amb";

const channelID = "/rw/default/:table/:filter";

export const createComponentWatcher = (prefix = "DEFAULT_PREFIX", options) => { 
    return createAmbSubscriptionEffect(channelID, {
        subscribeStartedActionType: `${prefix}_WATCHER_DEFAULT_STARTED`,
        subscribeSucceededActionType: `${prefix}_WATCHER_DEFAULT_SUBSCRIBED`,
        subscribeFailedActionType: `${prefix}_WATCHER_DEFAULT_FAILED`,
        unsubscribeSucceededActionType: `${prefix}_WATCHER_DEFAULT_UNSUBSCRIBED`,
        messageReceivedActionType: `${prefix}_WATCHER_DEFAULT_CHANGED`,
        ...options,
    });
};
Enter fullscreen mode Exit fullscreen mode

Now that we have our helper function set up we can quickly build actionHandlers to start or stop a record watcher subscription.

actionHandlers.js

import { createComponentWatcher } from "../ambEffectHelper";

export default {
    actionHandlers: {


        //┌─────────────────────────────────────────────────────────────
        //! This will beging the record watcher subscription
        //! an important detail to note is the subscribe: true
        //! in the dispatch call.
        //└─────────────────────────────────────────────────────────────
        START_RECORD_WATCHER: ({ state, dispatch }) => {
            const recordTable = "incident";
            const recordID = "7f96bd071b32c010f89b99bc1d4bcbf9";
            const originalFilter = `state=1^sys_id=${recordID}`;
            const filter = btoa(originalFilter).replace(/=/g, '-');
            dispatch(`SETUP_RECORD_WATCHER`, {
                table: recordTable,
                filter: filter,
                subscribe: true,
            });
        },


        //┌─────────────────────────────────────────────────────────────
        //! This actionHandler is nearly the same, but stops the
        //! record watcher instead of starting it.
        //└─────────────────────────────────────────────────────────────
        STOP_RECORD_WATCHER: ({ state, dispatch }) => {
            const recordTable = "incident";
            const recordID = "7f96bd071b32c010f89b99bc1d4bcbf9";
            const originalFilter = `state=1^sys_id=${recordID}`;
            const filter = btoa(originalFilter).replace(/=/g, '-');
            dispatch(`SETUP_RECORD_WATCHER`, {
                table: recordTable,
                filter: filter,
                subscribe: false,
            });
        },


        //┌─────────────────────────────────────────────────────────────
        //! This actionHandler allows you to start and stop
        //! a record watcher using our helper function.
        //└─────────────────────────────────────────────────────────────
        SETUP_RECORD_WATCHER: createComponentWatcher("RECORD", {
            subscribeStartedActionType: `RECORD_WATCHER_STARTED`,
            messageReceivedActionType: `RECORD_WATCHER_ITEM_CHANGED`,
        }),


        //┌─────────────────────────────────────────────────────────────
        //! This is really just to let you know a subscription
        //! to a record watcher has been started. Serves no other
        //! purpose.
        //└─────────────────────────────────────────────────────────────
        RECORD_WATCHER_STARTED: (coeffects) => {
            console.log(`RECORD_WATCHER_STARTED`, coeffects);
        },


        //┌─────────────────────────────────────────────────────────────
        //! This is the most important actionHandler, it will be
        //! triggered any time a record that fits the query is
        //! inserted/updated/deleted.
        //!
        //! From here you can perform any logic you want.
        //└─────────────────────────────────────────────────────────────
        RECORD_WATCHER_ITEM_CHANGED: ({ action, dispatch }) => {
            const { payload } = action;
            console.log(`RECORD_WATCHER_ITEM_CHANGED`, payload);
            const { data: { operation } } = payload;

            //! Do something based on the operation
            //! insert | update | delete
            dispatch(`RECORD_WATCHER_${operation}`, payload);
        },


    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

With this basic setup you can now perform any logic you want when a record is inserted, updated or deleted within the encodedQuery you define for your record watcher.

Since components can't always be directly attached to each other, this is a nice way to form a basic type of communication between different components.

Disclaimer

Again, the only way to truly test to see if your record watcher is working is to deploy your component to your instance, this a major detractor in the setup process, but with some effort you too can begin record watching!

Top comments (0)