The Now Platform® Washington DC release is live. Watch now!

Help
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
DrewW
Mega Sage
Mega Sage

So the first component that I have been working on has required me to make sure that I monitor a set of records for changes so that I can update the component with new information.  Now this information changes based on the current user or possibly other users in the background.  So in Service Portal I would use spUtil.recordWatch to do this.  Well there is no documentation that I could find on how to set this up for a component.  So I went and poked ServiceNow and I was told that there was something out on npm for AMB but its usage was not supported and there were no docs that could be passed on.  Well that's frustrating but it was enough information for me to find the @servicenow/ui-effect-amb and poke thru the code to start to figure it out.  After poking thru that I had to spend some time looking at how a workspace actually used the method since it was not as strait forward as I thought.

There are several methods that are in the ui-effect-amb but so far I have only been working with the createAmbSubscriptionEffect and that is the one I will talk about here.

 

Setup
So to setup the usage of it you may need to install it.

npm i @servicenow/ui-effect-amb

For those of you who did not know the code for the ui-effect-amb is now in your project under node_modules/@servicenow/ui-effect-amb, along with all of the others that you have installed.  So you can go thru the code there and see whats available whe.

Then you need to add the include for it in your code

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

Now you will see in the code I posted at the bottom I did two includes, the reason I did this was so I could dump out the object to the console and look at everything that was available by default.  So you can just delete lines 4 and 7 if you are not interested in seeing that.

 

Usage

You would think usage would be strait forward but I guess I do not use web services enough because it was not as strait forward as I thought it should be but once I got it to work I felt it should have been at least a little obvious.  So we setup the function so we know what Action Types to watch for.

const recordWatcherHandler = createAmbSubscriptionEffect('/rw/default/:table/:filter', {
	subscribeStartedActionType: 'RECORD_WATCHER_SUBSCRIBE_STARTED',
	subscribeSucceededActionType: 'RECORD_WATCHER_SUBSCRIBED',
	subscribeFailedActionType: 'RECORD_WATCHER_SUBSCRIBE_FAILED',
	unsubscribeSucceededActionType: 'RECORD_WATCHER_UNSUBSCRIBED',
	messageReceivedActionType: 'RECORD_WATCHER_ITEM_CHANGED'
});

Then we can add the action type to dispatch

actionHandlers: {
	'RECORD_WATCHER_SUBSCRIPTION_CHANGED': recordWatcherHandler,
}

Once we have those items its just a matter of calling the dispatch to setup the watcher.  I did this using COMPONENT_CONNECTED but you could use a button or other thing to trigger your dispatch. Once we have decided when/where we are going to call the dispatch we just need to make that happen.

dispatch('RECORD_WATCHER_SUBSCRIPTION_CHANGED', {
	table: state.table,
	filter: state.filter ? btoa(state.filter).replace(/=/g, "-") : "",
	subscribe: true
});

Notice what I did with the filter.  This was the item that was eluding me until I found it in the Workspace code.  After seeing this it made sense to me because the filter could be a lot of things and the REST call will not like most of them and could think that any question mark or equals was indicating something that it is not.  So you need to encode the filter into a Base64 string and convert any equals sign into a dash. 

 

Testing and issues

Testing this is fairly simple, you can use the code at the bottom to make a test component and simply change the initialState values to what ever you want. Then all you have to do is go into the instance and update a record and you will see in the console a RECORD_WATCHER_ITEM_CHANGED printed and the data passed to the effect function we setup right?  Well not so much.  What I found is that even after adding /amb and /rw to the proxies list in the now-cli.json file that I would get errors saying that the connection to the local host was interrupted and it could not make a connection.  I could not find a way to resolve this but I did find that pushing the component to the instance and adding it to a workspace allowed it to work and I would see the action types trigger when I would insert/update/delete records that met the supplied filter.

There is an issues to be aware of.  I could not find a way to distinguish one watcher from another that used the same filter.  So if you have a component that loads with a form, like as a ribbon item and the component is watching the same record as the same component loaded for a different form in a different tab on workspace that they appear to use the same watcher so if you make a call to unsubscribe it does so for all of the components using that same watcher.  I also noticed that if the component does load with with a form and there is already a watcher in place for the filter you are using that it does not trigger any of the action types for the second instance of the component.  Also I found that when a record is inserted/updated/deleted that it does not appear to call the action type one time for each component that is loaded.  Now this may be because the browser is smart enough to know that they are all rendering the same content so it does not need to do it three times it may not.  So more testing is needed to understand this better.

 

End remarks

If you know more about this than I please pass on what you have found and post it here, I would really like to know. As I find out more I will add to this article when I can.

 

Complete Code

import {createCustomElement, actionTypes} from '@servicenow/ui-core';
import snabbdom from '@servicenow/ui-renderer-snabbdom';
import styles from './styles.scss';
import * as ambObj from '@servicenow/ui-effect-amb'
import {createAmbSubscriptionEffect} from '@servicenow/ui-effect-amb';

console.log(ambObj);

const recordWatcherHandler = createAmbSubscriptionEffect('/rw/default/:table/:filter', {
	subscribeStartedActionType: 'RECORD_WATCHER_SUBSCRIBE_STARTED',
	subscribeSucceededActionType: 'RECORD_WATCHER_SUBSCRIBED',
	subscribeFailedActionType: 'RECORD_WATCHER_SUBSCRIBE_FAILED',
	unsubscribeSucceededActionType: 'RECORD_WATCHER_UNSUBSCRIBED',
	messageReceivedActionType: 'RECORD_WATCHER_ITEM_CHANGED'
});

const {
	COMPONENT_CONNECTED, 
	COMPONENT_ERROR_THROWN,
	COMPONENT_DISCONNECTED
	} = actionTypes;

const view = (state, {updateState}) => {
	return (
		<div className="">
			Section list:<br />
		</div>
	);
};

createCustomElement('mahe2-test-component', {
	renderer: {type: snabbdom},
	view,
	styles,
	actionHandlers: {
		[COMPONENT_CONNECTED]: {
			effect: ({state, dispatch}) => {
				//Subscribe
				dispatch('RECORD_WATCHER_SUBSCRIPTION_CHANGED', {
					table: state.table,
					filter: state.filter ? btoa(state.filter).replace(/=/g, "-") : "",
					subscribe: true
				});
				
			},
			stopPropagation: true
		},
		[COMPONENT_ERROR_THROWN]: ({...stuff}) => {
			console.log(stuff);
		},
		[COMPONENT_DISCONNECTED]:{
			effect: ({state, dispatch}) => {
				// unsubscribe
				dispatch('RECORD_WATCHER_SUBSCRIPTION_CHANGED', {
					table: state.table,
					filter: state.filter ? btoa(state.filter).replace(/=/g, "-") : "",
					subscribe: false
				});
			},
			stopPropagation: true
		},

		'RECORD_WATCHER_SUBSCRIPTION_CHANGED': recordWatcherHandler,
		'RECORD_WATCHER_SUBSCRIBE_STARTED': (...stuff) => {
			console.log(stuff);
			console.log("RECORD_WATCHER_SUBSCRIBE_STARTED");
		},
		'RECORD_WATCHER_SUBSCRIBED': (...stuff) => {
			console.log(stuff);
			console.log("RECORD_WATCHER_SUBSCRIBED");
		},
		'RECORD_WATCHER_SUBSCRIBE_FAILED': (...stuff) => {
			console.log(stuff);
			console.log("RECORD_WATCHER_SUBSCRIBE_FAILED");
		},
		'RECORD_WATCHER_UNSUBSCRIBED': (...stuff) => {
			console.log(stuff);
			console.log("RECORD_WATCHER_UNSUBSCRIBED");
		},
		'RECORD_WATCHER_ITEM_CHANGED': (...stuff) => {
			console.log(stuff);
			console.log("RECORD_WATCHER_ITEM_CHANGED");
		}
	
	},
	initialState: {
		table: 'u_bok_section',
		filter: 'u_active=true'
	}

});
Comments
BrianM
Tera Contributor

I just wanted to add that it's not recommended to use AMB when you expect a lot of data to be updated at once.  Say you are displaying a table, and 200 records get inserted.  AMB can auto-populate this table for you, but it will start to get very slow as it consumes more memory and can eventually lock up your browser tabs that are open to your SNOW instance.  To get around this I unsubscribe and put in regular httpEffect calls at an interval for a period of time, then re-subscribe to AMB for the smaller updates. 

Regarding insert/update/delete, you can see which one AMB is trying to do by checking the operation:

let resData = stuff[0].action.payload.data;
let operation = resData.operation;

Even though the response comes back as an array, I have never seen the array's length be anything but 1.

All of the record's data is contained in the "record" key, so you can pick out what you need.  When an update operation occurs, "record" will only contain the data that was updated and not every field of the record.  This data comes across with the value and display_value keys.

if(operation == "update"){
   let stateVal = resData.record.state.value;
   let stateDisplay = resData.record.state.display_value;
}

 

Edit: 

One last thing - unsubscribing from AMB never worked for me initially because by default it encodes the URI component for the channel ID.  To get unsubscribing to work, I had to set that property to false (last line below):

 
const taskRecordWatcherHandler = createAmbSubscriptionEffect('/rw/default/:table/:filter', {
    subscribeStartedActionType: 'TASK_RECORD_WATCHER_SUBSCRIBE_STARTED',
    subscribeSucceededActionType: 'TASK_RECORD_WATCHER_SUBSCRIBED',
    subscribeFailedActionType: 'TASK_RECORD_WATCHER_SUBSCRIBE_FAILED',
    unsubscribeSucceededActionType: 'TASK_RECORD_WATCHER_UNSUBSCRIBED',
    messageReceivedActionType: 'TASK_RECORD_WATCHER_ITEM_CHANGED',
    encodeURIComponentForChannelId: false,
});
Version history
Last update:
‎05-18-2020 02:20 PM
Updated by: