Auditing Flows using the Archy SDK

Hi,

I am involved in a project to try to audit the documentation of our Genesys Cloud environment, including Architect flows.

I have a Node utility which is using the standard API to extract various lists of objects and create a spreadsheet (at this stage, I'm looking to see if Descriptions are filled in etc.) and I need to move on to do the Architect flows.
OK, so I have three sticking points (the first of which is probably more to do with JavaScript than the API, but...)

  1. As I iterate through the flows, I'm hitting Rate Limits. I am unsure how to capture the HTTP response and then force a pause before retrying. I have already inserted a 1.5 second delay between flow fetches, but it still happens and I don't want to simply put in relatively random delays as it extends the run time of the code!
  2. One of main things I need to grab the Participant Data that is set by the flow, but as I scan for the SetParticipantData nodes, I can't figure out how to get the name(s) of the attribute(s) being set. I'm fairly sure that traverseInfo.matchObject.attributeNameValuePairs is an array describing the attributes, but I can't figure out what member contains the name!
  3. I also need to determine what items / nodes etc have been left with their default names. This is relatively easy for things like "Disconnect" since the default name is the same as the item type, but what about things like Tasks? I could look for the name beginning with "New" but I was hoping for a more elegant solution!

I'd appreciate any and all thoughts on this. I'm happy to post code, if that would help!

Paul

Hi Paul,

I am going to forward your question to our Architect team and see if they can you some help. First, if you are calling the platform APIs you should receive a 429 HTTP status code as your HTTP response code when you are getting rate limited. If you get a 429 you should check the HTTP Response headers and look at the value set in the retry-after header. This will tell you how many seconds your code should wait before trying again. Here is a link to an article on our development center about rate limits. We have a rate limit of 300 calls per minute for a single OAuth client and when you do hit this rate limit 1.5 seconds is usually not enough time to "cool" down from the rate limit.

Here are some additional materials around rate limiting and resiliency:

  1. Building resiliency into your Genesys Cloud Integrations. Blog post covering basic resiliency patterns for cloud-based integrations.
  2. Building resilient applications in Genesys Cloud. 45-minute webinar discussion going through the resiliency patterns.
  3. Github repository for building resilient applications in Genesys Cloud talk.
  4. Rate limiting DevDropdrop Video. Rate Limiting and the Genesys Cloud API. A short video discussion about how Genesys Cloud uses rate limits in their APIs.
  5. Using the Genesys Cloud Java SDK's retry logic. Short video overview of how to use our Java SDK's built-in retry logic.
  6. Using caching to mitigate rate-limits in Genesys Cloud. Short video on how to use caching to avoid the need for unnecessary API calls.

I think you and I exchanged some emails earlier, so I might be repeating myself but I am going to throw this out here for other forum users. Please forgive me if I already sent you this and like I said, I am going to forward this forum post to our architect team.

As for auditing the flows themselves, I don't know if you have seen them, but we do have a higher level SDK for querying and manipulating our flows. If you are not using I suggest you take look here.

Here’s one that traverses a flow and for all arch base values will write out the value text which would be expression text, variable references, and whatnot:

                // Spin through a bunch of stuff on the flow and write out

                // the valueText property value for ArchBaseValue instances.

                // Time to turn off verbose logging so we get nice output.  :)

                logger.logNotesVerbose = false;

                archInboundCallFlow.traverse(function(traverseInfo) {

                    if (traverseInfo.matchObject.isArchBaseValue) {

                        let valueText = traverseInfo.matchObject.valueText;

                        if (valueText !== undefined && valueText.length) {

                            // for readability on the console output

                            while (valueText.length < 43) {

                                valueText += ' ';

                            }

                            logger.logNote(valueText + ' / ' + traverseInfo.context.hierarchyStr);

                            //

                            // If you wanted traversal to stop, you can do that like this:

                            // traverseInfo.settings.shouldContinue = false;

                            //

                        }

                    }

                });

You can configure filters and other stuff about when you want to be called back or like in the code above, just look at the matchObject and see if it’s the appropriate object you want like isArchBaseObject above. That’s usually the easiest to just do filtering in the callback but you can create filters with clauses and various things to pass into the traverse call so you only, say, get called back on Get or Set Participant Data actions. For example, check to see if isArchActionGetParticipantData or isArchActionSetParticipantData is true on the callback and you know you’re being called back on a Get or Set participant data action. Then if you were on a Get Participant Data action, access the attributeNameOutputValuePairs property to get an ArchBaseNameValuePairs instance and you could then iterate through the attributes and output bindings that are set in the flow.

And obviously, as you did for iterating flows, you can do the same thing in Scripting and have it load up all flows, use traverse to march through them, and output this kind of information for all flows in an org.

Hi Paul,

For #2, you are definitely on the right track. You can use that attributeNameValuePairs property to get the individual ArchBaseNameValuePair values by utilizing the function AttributeNameValuePairs.getNamedValueByIndex (Class: ArchBaseNameValuePairs | Architect Scripting), which gives you back an ArchBaseNameValuePair instance. From there, that object instance will have a name and a value property on it you can check.

For #3, unfortunately, there's no built-in way to check. What you described is probably a good approach.

Thanks,
Scott

OK,

So first up, thank you both of you for your responses. Very much appreciated and yes, John, we did exchange some emails on this. It was thanks to those emails that I found the SDK and in the SDK documentation, it recommends using the forum. I figured I'd move here since others may have insights, others may benefit from your insights and I wanted to spread the load :wink:

OK, so on to the issue at hand. Beginning with the 429. I looked at the articles you referenced, John (some of which I was already aware of) however they all seem to deal with using the main Genesys Cloud API, rather than the Archy one. I understand the principle and purpose of a 429 and I also understand what I am supposed to do, the problem is I can't figure out how to do it in the SDK! :worried: You linked to an interesting video on the build-in functionality for the Java SDK and hinted that this was being added to the others. Has this, in fact, been done? If so, can you point me at the location in the docs where it is discussed (I can't seem to find it!) If not, then my question becomes "how do I detect the 429 response in order to deal with it appropriately?" Part of the challenge I have is that all these calls are async. My JavaScript-foo is still somewhat immature, so I haven't figured out how to wait for one iteration to complete before requesting the next. As I said, I am putting a delay into the loop, but as you said, that delay is insufficient! I could up it, but whatever value I use would be arbitrary and risks making the runtime excessive!

Moving on to Q2. I am pretty much doing what you say. I stated with the code you (John) sent me and which you included here. I then check for isArchActionGetParticipantData / isArchActionSetParticipantData and then have a loop to go through the attributeNameValuePairs property. Here's where I come unstuck. (Again, this might be my lack of foo!) From the Docs, attributeNameValuePairs is of type ArchBaseNameValuePairs. This has a length property and a Method getNamedValueByIndex(index) which returns a single name value pair of type ArchBaseNameValuePair. This in turn has a Member called name, of type ArchBaseValue. Now what? The ArchBaseValue has Methods for getting a variable or an expression. which would be the value, but not the string literal. So I'm lost. I know I'm missing something obvious, but the more I look at this, the more the words seem to blur! :open_mouth:

I also tried going down the path of exporting the flows as yaml files and then processing those. Despite not getting an error, no file gets written to disk! NO idea why - any clues?

Finally, and I know this is almost definitely a "Lack of foo" problem, but how do I wait for all the callbacks to complete before writing my audit file to disk? With the main audit, I am using Promise.all, but that doesn't seem to work here?

It seems to me that a large part of the problem (for me) is that the async nature of it all is getting in my way. The utility isn't "real time" as such it would probably be easier if I could write it synchronously. Are the actual API calls themselves documented anywhere? I appreciate all the work that has gone into the SDK (although, I would personally prefer it if the "style" was the same as the main API's SDK) but for my use-case, it might be easier to just call the endpoints myself? This would also allow me to use a different language / environment, if I so chose.

Hi Paul,

  1. Ahhh. I was wondering if you were using the platform API or the Scripting SDK. The Scripting SDK was built by the Architect team so I can't really talk to how they are handling their rate-limiting? @anon99530797 Can you weigh in on this.
  2. Paul I understand your frustration with the asynchronous nature of API. This is a reflection of the language rather then the SDK. I am Java developer so when I have to write Javascript, I weep (Typescript makes we weep, but way less then JavaScript :)). I personally like to use async..await. I wrote our user provisioning guide when I first started with the Developer Engagement team and I found myself having to do retry logic quite a bit. I ended up using the life-omic/attempts library to handle my retry logic. The project can be found here.

Here is an example of where I used async/await with the retry logic to handle retrys with delays. It greatly simplified my code.

Again. I hope that helps.

Thanks, John

Thanks again, John!
Yes, that is pretty much the same approach I'm using. The problem (if I am reading it correctly) is that whilst the Platform SDK returns a promise, the Scripting SDK uses a callback function. This is partially what I meant when I talked about the "style".
With promises, you can wait for them all to be resolved with Promise.all([]), but I can't figure out how to replicate that functionality when it's callback functions :wink: (As I said before, my JavaScript-foo is lacking!)
I tried having a global that I increment when I make the call, and decrement at the end of the callback function, then simply waiting at the end for the global to hit zero. Nope, the global doesn't get updated, presumably due to threading?
If anyone is able to explain how to do it, I'd be most grateful (I know it's a little off-topic, but...)

Hi Paul,

So for #2, when you get an ArchBaseNameValuePair (let's call the instance archBaseNameValuePair), you can access archBaseNameValuePair.value to get the ArchValueString for the value that was set (aka the literal that was set). On ArchValueString, you can use the valueText property to get the text set. So the whole thing, if you wanted to put it into one line, looks like this: setParticipantDataAction.attributeNameValuePairs.getNamedValueByIndex(0).value.valueText.

For your rate limiting / async question, within a single session, Scripting should be automatically handling retries on 429 responses. You can also use ArchAsyncTracker.allSettled, which returns a promise for all asynchronous operations being tracked by Scripting, if you want to await that for any reason (this should address your question about awaiting callbacks to complete). If you're seeing instances where 429s aren't being handled correctly, feel free to shoot over some details on what calls are getting 429'd, how they're being triggered, and in what scenarios it happens, and we can take a look.

For the YAML files not getting written to disk, you may want to open a Care ticket for that.

Also a random protip: most of (if not all of) the asynchronous Scripting functions (aka the ones that take callbacks and their names end with Async) can be await-ed instead of using callbacks. For example, for the ArchActionCallData.setDataActionByIdAsync function, you can do this:

await archActionCallData.setDataActionByNameAsync('myDataAction');
console.log('Successfully set the data action');
// ...the rest of your code

instead of this:

archActionCallData.setDataActionByNameAsync('myDataAction', (archActionCallDataFromCallback) => {
	console.log('Successfully set the data action');
	// ...the rest of your code
});

async / await for the win... way better than entering callback hell :slight_smile: Just don't forget try / catch blocks for handling any network errors, etc.

Thanks,
Scott

Thanks, Scott, I will certainly try what you recommend regarding the async/await cs callbacks!

I'm sorry to appear stupid here, but with the attribute name-value pairs, I am not trying to get the value, rather I need a list of the names. It seems (from the docs) that the ArchBaseNameValuePair.name member is not a string, but instead a ArchBaseValue, which doesn't seem to have a member representing a literal? Or am I missing something here?

Hi Paul,

You're on the right track with the ArchBaseNameValuePair.name property. You'd just use the valueText property on that to get the literal string representing name of the participant data set. So like this: setParticipantDataAction.attributeNameValuePairs.getNamedValueByIndex(0).name.valueText. It's like the other example I posted, but with name instead of value as the second-to-last property being accessed.

Thanks,
Scott

Thanks, Scott! That worked :slight_smile:

This topic was automatically closed 31 days after the last reply. New replies are no longer allowed.