Graphical workflow Archives - ServiceNow Guru https://servicenowguru.com/category/graphical-workflow/ ServiceNow Consulting Scripting Administration Development Thu, 18 Jul 2024 13:21:28 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.2 https://servicenowguru.com/wp-content/uploads/2024/05/cropped-SNGuru-Icon-32x32.png Graphical workflow Archives - ServiceNow Guru https://servicenowguru.com/category/graphical-workflow/ 32 32 Applying the Sync/Async Pattern in Flow Designer https://servicenowguru.com/graphical-workflow/applying-sync-async-pattern-flow-designer/ Thu, 18 Jul 2024 13:21:28 +0000 https://servicenowguru.com/?p=16846 Introduction Here is the scenario: you need to use up-to-date data from a record in a transaction that may take a long time to process, but you don’t want the user to have to wait for that transaction in the UI. You may, for example, need to send a Comment or Work Note to another

The post Applying the Sync/Async Pattern in Flow Designer appeared first on ServiceNow Guru.

]]>
Introduction

Here is the scenario: you need to use up-to-date data from a record in a transaction that may take a long time to process, but you don’t want the user to have to wait for that transaction in the UI. You may, for example, need to send a Comment or Work Note to another system through an API call. That can take a few seconds or more, and it is possible that multiple comments could be entered by the time you send the API call, all the while multiple API calls are stacking up. Let’s see how we can use a Flow Designer Sync/Async design pattern to optimize user experience.

One way to handle this is by accomplishing part of this action synchronously within the “Business Rule Loop” by logging an Event and stashing the data you need (for example, the Comment or Work Note) in an event parameter so that it is up to date at the time of the transaction, and then processing the longer portion asynchronously outside of the client transaction using a Script Action. This Sync/Async design pattern ensures that you are capturing the data you want to send and returning control to the User as quickly as possible while still being able to send that “correct” data regardless of how long it takes to process to the other system.

Is it possible to accomplish this same pattern using low- or no-code capabilities with Flow Designer? It is!
Use a Sync/Async design pattern to optimize user experience.

If you are not familiar with Flows and Subflows, you may want to check out the documentation first.

Foreground Flows for Synchronous Processing

By default, Flows are set to run in the background so that they do not slow down the user experience. This means that they do not run as part of the Before/After Business Rule transaction (the Business Rule Loop mentioned previously), but instead run outside of the transaction. There is an option to run Flows in the Foreground, which will run them during the Business Rule Loop and make them part of the Client transaction. It is important to make these types of Flows as efficient as possible to avoid negatively impacting the user experience.

One benefit for running Flows in this way is that you have access to the Trigger Record at runtime, so the data the user changed is available to you immediately. If you try to retrieve a Journal Entry asynchronously, it is possible that another entry has been made by the time you try to retrieve it, so you cannot guarantee that you have the right data when the asynchronous code executes.

Our first requirement, getting the “right” Comment or Work Note, is met by retrieving it in a Foreground Flow. Let’s try this out by creating a Flow that will run in the Foreground when an Incident has a new Comment and log the Comment. We will add to this later, but for now we just want to get this up and running.

‘Sync Flow on Incident Comment’ Flow
Flow name: Sync Flow on Incident Comment
Trigger: Record > Created or Updated
Table: Incident
Condition: Active is true AND Additional comments changes
Run Trigger: For each unique change
Advanced Options
Where to run the flow: Run flow in foreground
ACTIONS

  1. Action: Log
    • Level: Info
    • Message (toggle scripting for this input):

      // Retrieve the most recent Comment from the Incident
      return "From the Flow:\n\n" + fd_data.trigger.current.comments.getJournalEntry(1);
      

This log will show up in the system log table with a “Source” value of “Flow Designer.” Activate this flow and test it out by adding a Comment to any active Incident and checking the logs.

Note that you could create a custom Action to retrieve the Journal Entry from the Incident to provide a no-code method to retrieve the data for future use, but for now we’ll stick with using a script in the Message field to keep things simple.

Great! Now we can get the comment, but how do we do something with it without having to make the user wait until we are done?

Subflow With Inputs

We will use a Subflow to handle the asynchronous portion. To do this, we need to pass our synchronously obtained data to the Subflow so that it does not have to retrieve potentially newer information when it runs. We’ll cover how to make this Subflow run asynchronously from the Flow a bit later.

For this example, we will simulate a potentially slow external API call by adding a wait timer to our Subflow.

First, we create a new Subflow and add inputs for the Incident record and the Comment that we want to handle in the API call. This ensures that we have access to the comment that was entered at the time we trigger the Subflow and that we are not retrieving a more recently entered comment when the Subflow executes.

Next, we log a timestamp for when the Subflow begins processing. Then we add a 10-second Wait (you can increase this if you want, to allow yourself time to enter multiple Comments while the Subflow waits to execute during your testing). Finally, we log out the Comment from the Subflow Input. We can also add another log step where we repeat the script we used to retrieve the most recent Comment from the Incident to see if it differs from the Comment we received as a Subflow Input.

‘Async Subflow Handle Incident Comment’ Subflow
Subflow name: Async Subflow Handle Incident Comment
INPUTS & OUTPUTS

  1. First Input
    • Label: Incident
    • Name: incident
    • Type: Reference.Incident
    • Mandatory: True
  2. Second Input
    • Label: Comment
    • Name: comment
    • Type: String
    • Mandatory: True

ACTIONS

  1. Action: Log
    • Level: Info
    • Message: Async Subflow, before Wait.
  2. Flow Logic: Wait for a duration of time
    • Duration Type: Explicit duration
    • Wait for: 10s
  3. Action: Log
    1. Level: Info
    2. Message: From subflow, after wait. Input comment: [data pill] Subflow Inputs > Comment [data pill]
  4. Action: Log
    1. Level: Info
    2. Message (toggle scripting for this input):
      // Log out the most recent journal entry from the Incident
      return 'From subflow, after wait. Most recent journal entry:\n\n' + fd_data.subflow_inputs.incident.comments.getJournalEntry(1);
      

Notice that we do not create any Subflow outputs. Because we plan to run this asynchronously, the Flow that calls our Subflow will no longer be running when the Subflow completes and will not be able to do anything with any outputs. Additionally, any error handling for the actions carried out by the Subflow needs to be contained within the Subflow and not passed to the calling Flow for additional handling. For example, you may usually pass the API response status and body back to the calling Flow to check for and respond to any errors (like creating an Incident), but you will now need to do this within the Subflow.

Publish the Subflow to make it available for use. We now have the Asynchronous component of our Flow Designer Sync/Async design pattern.

Subflow component of the Flow Designer Sync/Async design pattern to optimize user experience.

Final Subflow, with Inputs shown in detail.

Call the Subflow from the Flow

We now have a Subflow to handle the Comment, but we need to add it to the Flow to complete our Flow Designer Sync/Async design. Add a new step to the Flow, select the “Subflow” option, and then locate the Subflow you created. Set the Inputs to be the Incident trigger record and the most recent Comment (the same value you passed into the Log step, which we scripted).

To make this Subflow run Asynchronously, make sure that the “Wait for Completion” checkbox is unchecked. Doing so will allow the Flow to complete and immediately return control to the Client transaction and complete the remainder of the Business Rule Loop without waiting for the Subflow.

For testing, we will add another Log step after we call the Subflow to observe the sequence of events.

Your Flow should now look like this:

‘Sync Flow on Incident Comment’ Flow
Flow name: Sync Flow on Incident Comment
Trigger: Record > Created or Updated
Table: Incident
Condition: Active is true AND Additional comments changes
Run Trigger: For each unique change
Advanced Options
Where to run the flow: Run flow in foreground
ACTIONS

  1. Action: Log
    • Level: Info
    • Message (toggle scripting for this input):

      // Retrieve the most recent Comment from the Incident
      return "From the Flow:\n\n" + fd_data.trigger.current.comments.getJournalEntry(1);
      
  2. Subflow: Async Subflow Handle Incident Comment
    • Wait For Completion: False
    • Incident: [data pill] Trigger > Incident Record [data pill]
    • Comment (toggle scripting for this input):
      // Return the most recent journal entry
      return fd_data.trigger.current.comments.getJournalEntry(1);
      
  3. Action: Log
    • Level: Info
    • Message: From sync flow, after calling Subflow.
Final Flow using a Flow Designer Sync/Async design pattern to optimize user experience.

Final Flow, showing the values passed to the Subflow inputs in detail.

NOTE: After testing this multiple times, I discovered that the “Wait for a duration of time” block in the Subflow is necessary to make the Subflow truly Asynchronous. Testing without the timer indicates that the main Flow will still hang while the Subflow is running even though the “Wait for Completion” box is unchecked. It is unclear whether this is intended behavior. For now, I recommend adding a one-second wait to the Subflow while also unchecking the “Wait for Completion” box when calling the Subflow to ensure that the Client transaction does not pause for the Subflow to complete. You are welcome to try this out on your own. I used gs.gleep() in the script for one of the Subflow Log steps instead of the “Wait for a duration of time” step and observed that the client transaction did appear to hang even when the “Wait For Completion” box was unchecked for the Subflow.

Summary

Now we have seen how to implement a Flow Designer Sync/Async design pattern that would normally be accomplished using Events and Script Actions. This gives us another tool to use for optimizing the User Experience while also ensuring access to accurate data for long-running asynchronous transactions.

The post Applying the Sync/Async Pattern in Flow Designer appeared first on ServiceNow Guru.

]]>
Workflow Join Activity https://servicenowguru.com/graphical-workflow/workflow-join-activity/ https://servicenowguru.com/graphical-workflow/workflow-join-activity/#comments Wed, 09 Oct 2013 13:40:03 +0000 https://servicenowguru.wpengine.com/?p=5054 A few weeks ago I came across an issue involving a Workflow Join Activity and some confusion around what it does and how it works. There is a little bit of documentation on the wiki but it still left some questions unanswered. The purpose of the Join Activity is to combine two or more paths

The post Workflow Join Activity appeared first on ServiceNow Guru.

]]>
A few weeks ago I came across an issue involving a Workflow Join Activity and some confusion around what it does and how it works. There is a little bit of documentation on the wiki but it still left some questions unanswered.

The purpose of the Join Activity is to combine two or more paths of execution after they have branched out. This serves the purpose of ensuring that subsequent activities will not get run multiple times with unexpected results.

A Join activity will always wait for all active paths of execution to reach it before proceeding.

Join Activity-Path still executing

The difference between the Complete and Incomplete outputs comes down to whether or not all possible paths of execution have completed. For example, if there is a split to have two simultaneous tasks on parallel paths then after both tasks have completed the Join will finish with Complete.

Join Activity-Complete Example

To contrast that, when there is an if statement, only one path will ever actually get executed so the Join will always exit as Incomplete.

Join Activity-Incomplete Example

Hopefully that clarifies things a bit for you; it certainly helped me to review how it worked.

The post Workflow Join Activity appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/graphical-workflow/workflow-join-activity/feed/ 8
Percentage or Majority-based Workflow Approvals https://servicenowguru.com/graphical-workflow/approval-percentage/ https://servicenowguru.com/graphical-workflow/approval-percentage/#comments Thu, 31 Jan 2013 19:12:58 +0000 https://servicenowguru.wpengine.com/?p=4697 One very common service request or change request approval requirement is to ask for a percentage or majority-based approval. This is something that ServiceNow workflow can do, but it requires a bit of scripting. In the following article, I’ll show you how you can set up some simple scripts in your graphical workflow ‘Approval’ activities

The post Percentage or Majority-based Workflow Approvals appeared first on ServiceNow Guru.

]]>
One very common service request or change request approval requirement is to ask for a percentage or majority-based approval. This is something that ServiceNow workflow can do, but it requires a bit of scripting. In the following article, I’ll show you how you can set up some simple scripts in your graphical workflow ‘Approval’ activities to handle these scenarios for any percentage you choose.

ServiceNow Majority Approval

Looking for other approval script examples? Check out this article about change management approval scripts!

 

The following example scripts are designed to be placed directly in the ‘Approval Script’ field on a workflow ‘Approval’ activity as shown in the screenshot above. The ‘Approval Script’ field is only visible if you select ‘Condition based on script’ from the ‘Wait for’ field. Once you paste the script in, just adjust the ‘approvePercent’ variable for the specific approval percentage needed.

Percentage-based approval with an ‘Approval – User’ activity

This first example assumes you’ve got a group of users grouped into a single workflow activity. These users could all be members of a single group. In fact, this is how I typically do majority approvals for the Change Advisory Board. Regardless of whether the users come from a group or are added individually, they are all combined together when evaluating counts in an ‘Approval – User’ activity.

//Approve based on percentage indicated
var approvePercent = 50;
if((counts.approved/counts.total)*100 >= approvePercent){
answer = 'approved';
}
if((counts.rejected/counts.total)*100 > (100 - approvePercent)){
answer = 'rejected';
}

Percentage-based approval with an ‘Approval – Group’ activity

It is also possible (albeit a bit more complex) to do percentage-based approvals with an ‘Approval – Group’ activity. The primary difference here is that you can require a certain percentage from multiple groups. In the example given, each individual group must meet the 50% approval threshold before the activity will return ‘Approved’ and tell the workflow to advance. Any single group not meeting the 50% mark will force a rejection for the entire activity. This took me way more time than it should have to figure out :). Hopefully it saves you a bit of time.

//Approve based on percentage from each group
var approvePercent = 50;
var approveCount = 0;
for(var id in groups){
var group = groups[id];
if((group.approved/group.total)*100 >= approvePercent){
//Mark the group approval record 'Approved'
var groupApproval = new GlideRecord('sysapproval_group');
groupApproval.get(id);
groupApproval.approval = 'approved';
groupApproval.update();
approveCount++;
}
if((group.rejected/group.total)*100 > (100 - approvePercent)){
answer = 'rejected';
break;
}
}
//Ensure all groups have approved
if(approveCount == counts.total){
answer = 'approved';
}

The post Percentage or Majority-based Workflow Approvals appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/graphical-workflow/approval-percentage/feed/ 12
Edit a Workflow Directly from a Task https://servicenowguru.com/ui-actions-system-ui/edit-workflow-ui-action/ https://servicenowguru.com/ui-actions-system-ui/edit-workflow-ui-action/#comments Wed, 22 Feb 2012 15:57:57 +0000 https://servicenowguru.wpengine.com/?p=4274 A couple of weeks ago, I saw a great idea from Alex Yupanqui, who works for ServiceNow, to create UI actions to allow users to directly edit a graphical workflow from the associated record. I’ve taken this idea and cleaned things up a bit to make it usable and secure throughout the system. You’re probably

The post Edit a Workflow Directly from a Task appeared first on ServiceNow Guru.

]]>
A couple of weeks ago, I saw a great idea from Alex Yupanqui, who works for ServiceNow, to create UI actions to allow users to directly edit a graphical workflow from the associated record. I’ve taken this idea and cleaned things up a bit to make it usable and secure throughout the system.

You’re probably already familiar with the ‘Show Workflow’ and ‘Workflow Context’ UI action links that show up on task records when a workflow gets associated to it. These UI actions are extremely useful for identifying the state of the workflow as it relates to the task. As an administrator or consultant, you’re often trying to troubleshoot or fix an issue with the workflow, which requires the workflow editor. Unfortunately, this means you have to navigate to the module in the left nav and try to find the correct workflow to edit. The purpose of this solution is to cut out all of those extra steps to allow you to edit the workflow directly from the record it is bound to.

Edit Workflow

Here’s the UI action code. Notice that it’s set on the ‘Global’ table so that it will be available as a link on every form in the system. You’ll also notice that the ‘Condition’ field is set to allow access to only the ‘workflow_admin’ role, and to only display the link when a record has a workflow associated to it.

‘Edit Workflow’ UI Action
Name: Edit Workflow
Table: Global
Action name: show_workflow_editor
Order: 205
Form link: True
Client: True
Onclick: showWorkflowEditor()
Condition: gs.hasRole(‘workflow_admin’) && new Workflow().hasWorkflow(current)
Script:

function showWorkflowEditor(){
//Open the workflow editor in a new window
var wf = new GlideRecord('wf_context');
wf.addQuery('id', g_form.getUniqueValue());
wf.query();
if(wf.next()){
getTopWindow().popupOpenFocus('workflow_ide.do?sysparm_sys_id=' + wf.workflow_version, 'show_workflow_version', 950, 700, '', false, false);
}
}

If you’ve set this global UI action up correctly, clicking the ‘Edit Workflow’ link will take you directly to the workflow editor for that specific workflow just as if you would have navigated through the module in the left nav.

Bonus UI Action

Here’s another UI action that you can use on the ‘wf_context’ table so that you have a quick link to edit the workflow when viewing a given workflow context. The basic ideas are the same, but the script and condition are a little bit different because we’re initiating the UI action from a specific table in this case.

‘Edit Workflow’ UI Action
Name: Edit Workflow
Table: Global
Action name: show_workflow_editor
Order: 105
Form link: True
Client: True
Onclick: showWorkflowEditor()
Condition: gs.hasRole(‘workflow_admin’)
Script:

function showWorkflowEditor(){
//Open the workflow editor in a new window
var id = g_form.getValue('workflow_version');
getTopWindow().popupOpenFocus('workflow_ide.do?sysparm_sys_id=' + id, 'show_workflow_version', 950, 700, '', false, false);
}

The post Edit a Workflow Directly from a Task appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/ui-actions-system-ui/edit-workflow-ui-action/feed/ 2
Exporting Graphical Workflows in One Step https://servicenowguru.com/system-definition/exporting-graphical-workflows/ https://servicenowguru.com/system-definition/exporting-graphical-workflows/#comments Tue, 14 Dec 2010 18:08:50 +0000 https://servicenowguru.wpengine.com/?p=3096 While this article is still extremely useful as an example and a reference for exporting records and use of processors, the specific functionality for exporting workflow versions is now included by default in ServiceNow. The functionality was built-in starting with the Aspen release. I’ve written before about how you can quckly export and import data

The post Exporting Graphical Workflows in One Step appeared first on ServiceNow Guru.

]]>
While this article is still extremely useful as an example and a reference for exporting records and use of processors, the specific functionality for exporting workflow versions is now included by default in ServiceNow. The functionality was built-in starting with the Aspen release.

I’ve written before about how you can quckly export and import data between ServiceNow instances using the XML export/import context menus. While this works great for a lot of situations, it doesn’t work so great for data that resides in multiple tables, but really makes sense to be exported as a single unit. One example of this is Graphical Workflows. The components that make up a Graphical Workflow version are actually stored in 7 separate tables. It is possible to export a graphical workflow version but you have to do 7 separate exports to do it! In this post, I’ll show you how you can set up UI actions for some of these more complex export tasks.


The first step is to set up a ‘Processor’ record to handle the export. ServiceNow actually has some built-in code to handle exports like this. You just have to know how to leverage it. Here’s how you would set up a processor for the workflow export.

‘ExportWorkflow’ Processor
Name: ExportWorkflow
Type: Script
Path: export_workflow
Script:

//Get the sys_id of the workflow version
var sysID = g_request.getParameter('sysparm_sys_id');
//Get the sys_id of the workflow
var wfID = g_request.getParameter('sysparm_wf_id');
var actID = '';//Query for workflow activities
var act = new GlideRecord('wf_activity');
act.addQuery('workflow_version', sysID);
act.query();
while(act.next()){
actID = actID + ',' + act.sys_id.toString();
}//Export workflow version info to XML
var exporter = new ExportWithRelatedLists('wf_workflow_version', sysID);
exporter.addRelatedList('wf_stage', 'workflow_version');
exporter.addRelatedList('wf_activity', 'workflow_version');
exporter.addRelatedList('wf_condition', 'activity.workflow_version');
exporter.addRelatedList('wf_transition', 'to.workflow_version');
exporter.addRelatedList('wf_transition', 'from.workflow_version');
exporter.addRelatedList('wf_workflow_instance', 'workflow_version');
if(wfID != ''){
exporter.addQuerySet('wf_workflow', 'sys_id=' + wfID);
}
if(actID != ''){
exporter.addQuerySet('sys_variable_value', 'document_keyIN' + actID);
}
exporter.exportRecords(g_response);

Once you have your processor set up, you just need to call it. The processor above is called by its path name ‘export_workflow’ followed by ‘.do’. It also needs to know what to export so you need to pass it the sys_id of the top-level record that you want to export…in this case, the sys_id of the ‘Workflow context’ record.

‘Export to XML’ UI Action
Name: Export to XML
Table: Workflow Version (wf_workflow_version)
Order: 200
Form context menu: True
Hint: Export workflow for importing in another instance
Condition: gs.hasRole(“admin”)
Script:

action.setRedirectURL('export_workflow.do?sysparm_sys_id=' + current.sys_id + '&sysparm_wf_id=' + current.workflow);

Once you’re done with this, you should have an ‘Export to XML’ UI action context menu item on your ‘Workflow version’ form that you can use to transport workflows in the event that they don’t get captured in an update set.

The post Exporting Graphical Workflows in One Step appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/system-definition/exporting-graphical-workflows/feed/ 34
Change Management Workflow Approval Scripts in ServiceNow https://servicenowguru.com/scripting/change-management-workflow-approval-scripts-servicenowcom/ https://servicenowguru.com/scripting/change-management-workflow-approval-scripts-servicenowcom/#comments Wed, 20 Oct 2010 16:25:52 +0000 https://servicenowguru.wpengine.com/?p=2193 Service-now.com provides a really cool Graphical Workflow engine to help manage workflows for different tasks. This graphical workflow engine is particularly useful when working with approvals for Change Requests. As a ServiceNow consultant I’ve found that change approvals usually fall into just a few different types but new administrators and consultants sometimes don’t know the

The post Change Management Workflow Approval Scripts in ServiceNow appeared first on ServiceNow Guru.

]]>
Service-now.com provides a really cool Graphical Workflow engine to help manage workflows for different tasks. This graphical workflow engine is particularly useful when working with approvals for Change Requests. As a ServiceNow consultant I’ve found that change approvals usually fall into just a few different types but new administrators and consultants sometimes don’t know the best way to implement approvals. In this post I’ll share some of the common change workflow approval methods and scripts I’ve seen used before. I wouldn’t be surprised to see this list grow over time and I know I haven’t seen all of the common methods. If you have something you’ve used before please comment on this post or use the ‘Contact’ link above to send in your suggestion to share.

Simple Approvers Selection (Select Specific Group)

The simplest (and most common) type of approval is to select specific groups or users to approve the change. The screenshot below shows how you can use the standard reference field lookup to select a group to add as approvers to the change request. This method is commonly used when you want to have a specific group (like the CAB) approve at some point in your workflow.

Simple Approvers Selection (Drill to Related Records)

Another common method is to drill into the change request itself and pull the approvers from a field on the change. This method is commonly used when you want to have something like a manager approval for the person requesting the change (as shown below) or maybe when you want to have the assignment group on the change request approve.

Advanced Approvers Script (Approval Groups for all Change CIs)

The ServiceNow Graphical Workflow mechanism also allows you be even more complex in the selection of approvals for your change requests. You can use script with GlideRecord queries to return any users or groups you want to have approve. The one thing to keep in mind with these scripts is that you need to return group records if your workflow activity is a Group Approval type and user records if your workflow activity is a User Approval type.
One common request I’ve seen before that requires a script is to add approval groups based on the Configuration Items associated to the change request. Chances are you’ll have multiple CIs associated on the ‘Affected CIs’ related list for the change. This script queries for those records and then adds the groups from the ‘Approval Group’ field on each CI.

//Initialize an answer array to be returned
var answer = [];

//Add the primary CI approval group to the array
answer.push(current.cmdb_ci.change_control);

//Add the Affected CIs list approval groups to the array
var affCIs = new GlideRecord('task_ci');
affCIs.addQuery('task', current.sys_id);
affCIs.query();
while(affCIs.next()){
answer.push(affCIs.ci_item.change_control);
}

Advanced Approvers Script (Approval Groups for all Impacted Change CIs)

There are some rare cases where clients want to pull approval groups from other CIs that will be impacted by the change based on CI relationships. For these cases you can use the CIUtils2 script I created to walk the CI relationship tree and find the impacted CIs that you need to add approvals for. In order to use these scripts you’ll need to add the CIUtils2 script include to your system first. You’ll also want to use this type of approval method sparingly since it involves a sometimes resource-intensive traversal of your CMDB (if you have hundreds of thousands of CIs). It’s also pretty expensive process-wise as well. Do you really want to require approval from every single group in your company when your network team needs to make a change to the core router? Maybe so, maybe not, but either way you’re probably talking about a lot of approvals because that CI impacts so many other CIs in your environment.

Impacted CIs approval scripts
–This first example returns Approval Groups for all of the Business Services impacted by the change request–

//Initialize a variable for the CIUtils2 Script include
var ciu = new CIUtils2();
//Initialize an answer array to be returned
var answer = [];//Add any impacted business services for the change
var allCIs = ciu.cisAffectedByTask(current);

//Query for all CIs and return Approval Groups
for (var i = 0; i < allCIs.length; i++) {
var cis = new GlideRecord('cmdb_ci');
cis.addQuery('sys_id', allCIs[i]);
cis.query();
if (cis.next()) {
answer.push(cis.change_control);
}
}

–This example returns Approval Groups for all of the Business Services impacted by the change request AND Approval Groups for all directly Affected CIs on the change request. It utilizes the ‘CheckDuplicates‘ function I wrote to eliminate some unnecessary processing.–

//Initialize a variable for the CIUtils2 Script include
var ciu = new CIUtils2();
//Initialize an answer array to be returned
var answer = [];
//Initialize an array to gather and store all impacted CIs
var allCIs = [];

//Add the primary CI to the CI array
allCIs.push(current.cmdb_ci);

//Add the Affected CIs list to the CI array
var affCIs = new GlideRecord('task_ci');
affCIs.addQuery('task',current.sys_id);
affCIs.query();
while (affCIs.next()) {
allCIs.push(affCIs.ci_item);
}

//For each directly affected CI on the change, add any impacted business services
for (var i = 0; i < allCIs.length; i++) {
allCIs = allCIs.concat(ciu.cisAffectedByCI(allCIs[i]));
}

//Remove duplicate CIs from the array
allCIs = checkDuplicates(allCIs);

//Query for all CIs and return Approval Groups
for (var j = 0; j < allCIs.length; j++) {
var cis = new GlideRecord('cmdb_ci');
cis.addQuery('sys_id', allCIs[j]);
cis.query();
while (cis.next()) {
answer.push(cis.change_control);
}
}

function checkDuplicates(a) {
//Check all values in the incoming array and eliminate any duplicates
var r = []; //Create a new array to be returned with unique values
//Iterate through all values in the array passed to this function
o:for(var i = 0, n = a.length; i < n; i++){
//Iterate through any values in the array to be returned
for(var x = 0, y = r.length; x < y; x++){
//Compare the current value in the return array with the current value in the incoming array
if(r[x]==a[i]){
//If they match, then the incoming array value is a duplicate and should be skipped
continue o;
}
}
//If the value hasn't already been added to the return array (not a duplicate) then add it
r[r.length] = a[i];
}
//Return the reconstructed array of unique values
return r;
}

Ensuring someone approves

One of the big problems you may encounter is what happens if the approvers don’t exist or aren’t active. The behavior in ServiceNow is to mark the approval activity as ‘Approved’ if no approvers are returned by the activity.

Change admin failsafe approval activity
–This script looks to see if an approver is returned, and if not it pulls in the ‘Change admins’ group as approvers for the approval activity.–

// Set the variable 'answer' to a comma-separated list of user/group ids or an array of user/group ids to add as approvers.
//
// For example:
// var answer = [];
// answer.push('id1');
// answer.push('id2');var answer = [];
//Requested by and assignment group manager approval
if(current.requested_by.manager.active){
answer.push(current.requested_by.manager.sys_id);
}
if(current.assignment_group.manager.active){
answer.push(current.assignment_group.manager.sys_id);
}

//Ensure 'Change Admins' group gets added if no other approvers (optional)
if(answer.length == 0){
var grpName = 'Change Admins';
//Query for change admins group
var cGrp = new GlideRecord('sys_user_group');
cGrp.get('name', grpName);
answer.push(cGrp.sys_id);
}

Making a specific person the key approver

Some approval scenarios may also require that a specific individual has the full approval/rejection responsibility even as a member of a group. Even though others can approve or reject, a single person has the final say. Once they approve or reject, we move on from the activity. The following script can be used in an ‘Approval – User’ workflow activity to facilitate this type of setup.

Key Person Approval workflow activity
This script should be placed in the ‘Approval Script’ field in a ‘Approval – User’ workflow activity. The ‘Approval Script’ field displays when the ‘Wait for’ field is set to ‘Condition based on script’ as shown in the following screenshot.

//'keyApproverID' must approve or reject to advance approval
//Set the 'keyApproverID' variable to the sys_id of the user record of the key approver
var keyApproverID = '97000fcc0a0a0a6e0104ca999f619e5b';
if (approvalIDs) {
if (approvalIDs['approved'].indexOf(keyApproverID) > -1) {
answer = 'approved';
}
if (approvalIDs['rejected'].indexOf(keyApproverID) > -1) {
answer = 'rejected';
}
}

The post Change Management Workflow Approval Scripts in ServiceNow appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/scripting/change-management-workflow-approval-scripts-servicenowcom/feed/ 18
Wait for Closure of all Tasks in Graphical Workflow https://servicenowguru.com/graphical-workflow/wait-closure-tasks-graphical-workflow/ https://servicenowguru.com/graphical-workflow/wait-closure-tasks-graphical-workflow/#comments Mon, 16 Aug 2010 14:57:14 +0000 https://servicenowguru.wpengine.com/?p=1909 A common Graphical Workflow requirement in ServiceNow is to tell the workflow to wait for some trigger before continuing. The ‘Wait For condition’ activity is available out-of-box and is very simple to configure. Usually when working with Service Requests or Change Requests I am asked how you can set up the workflow to wait for

The post Wait for Closure of all Tasks in Graphical Workflow appeared first on ServiceNow Guru.

]]>
A common Graphical Workflow requirement in ServiceNow is to tell the workflow to wait for some trigger before continuing. The ‘Wait For condition’ activity is available out-of-box and is very simple to configure. Usually when working with Service Requests or Change Requests I am asked how you can set up the workflow to wait for completion of all associated tasks before closing the Request Item or Change Request ticket. This can be easily accomplished by using a script in your ‘Wait For condition’ activity to query for any associated tasks that are still marked as ‘Active’. If the task is marked as ‘Active’ then it hasn’t been closed yet and the workflow should wait for that closure. Here are some sample scripts that I’ve used before to wait for task completion on both Request Items and Change Requests.


The scripts below can be pasted into the ‘Wait For condition’ activity script field as shown here…

Request Item ‘Wait For’ Scripts
Request Item – Wait for Closure of All Tasks:

//Query for all tasks to see if they are inactive
var rec = new GlideRecord('sc_task');
rec.addQuery('request_item', current.sys_id);
rec.addQuery('active', true);
rec.query();
if(rec.hasNext()){
answer = false;
}
else{
//Continue
answer = true;
}

Request Item – Wait for Closure of Workflow-generated Tasks:

//Query for all tasks to see if they are inactive
var rec = new GlideRecord('sc_task');
rec.addQuery('request_item', current.sys_id);
rec.addNotNullQuery('wf_activity');
rec.addQuery('active', true);
rec.query();
if(rec.hasNext()){
answer = false;
}
else{
//Continue
answer = true;
}
Change Request ‘Wait For’ Scripts
Change Request – Wait for Closure of All Tasks:

//Query for all tasks to see if they are inactive
var rec = new GlideRecord('change_task');
rec.addQuery('change_request', current.sys_id);
rec.addQuery('active', true);
rec.query();
if(rec.hasNext()){
answer = false;
}
else{
//Continue
answer = true;
}

Change Request – Wait for Closure of Workflow-generated Tasks:

//Query for all tasks to see if they are inactive
var rec = new GlideRecord('change_task');
rec.addQuery('change_request', current.sys_id);
rec.addNotNullQuery('wf_activity');
rec.addQuery('active', true);
rec.query();
if(rec.hasNext()){
answer = false;
}
else{
//Continue
answer = true;
}
Whether or not a task is checked in a workflow depends on the ‘Parent’ association as defined in the ‘SNC – Run parent workflows’ business rule on the task table. You may have to modify that business rule or create a modified version of it for the tables you’re working with. The logic behind the business rule is that it waits for certain closure conditions on child tasks and then runs workflows on any associated parents to check for things like ‘Wait for’ conditions. If your child tasks don’t meet these conditions then the parent task workflow will not be triggered when updates to the tasks are made.

The post Wait for Closure of all Tasks in Graphical Workflow appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/graphical-workflow/wait-closure-tasks-graphical-workflow/feed/ 24
Managing Ad-hoc Tasks in ServiceNow Workflow https://servicenowguru.com/graphical-workflow/managing-adhoc-tasks-servicenow-workflow/ https://servicenowguru.com/graphical-workflow/managing-adhoc-tasks-servicenow-workflow/#comments Tue, 12 Jan 2010 22:19:25 +0000 https://servicenowguru.wpengine.com/?p=656 This post is written in response to a question I received from a reader about how to handle ad-hoc tasks when you’re using graphical workflow. I’m always open to suggestions on how to improve the site and its content. If you have any ideas, questions, or suggestions for the site just use the Contact link

The post Managing Ad-hoc Tasks in ServiceNow Workflow appeared first on ServiceNow Guru.

]]>
This post is written in response to a question I received from a reader about how to handle ad-hoc tasks when you’re using graphical workflow. I’m always open to suggestions on how to improve the site and its content. If you have any ideas, questions, or suggestions for the site just use the Contact link to submit them. Thanks Ruth!

“The requirement is to add an extra task to a set of tasks defined in a graphical workflow once it is running AND have a way of specifying the order AND have the new tasks start automatically when its predecessor completes and start the next one when it completes. In the days of execution/delivery plans I had done this using a rule which set the new predecessor/successor entries but can anyone advise how to approach it with workflow?”

This isn’t an easy problem to solve. I still don’t know if I’ve found a really good way to approach it yet, but here are a couple of things I’ve done in the past (that are currently being used in production systems) that may give you some ideas. Take it for what it’s worth. These probably aren’t perfect solutions, but it’s better than nothing I suppose! I’d love to hear of any feedback you have as you try these out (if you do) because this is an issue that I’m probably going to have to tackle in another month or so again.

Option #1:

Using execution plans and a custom business rule to allow insertion and re-ordering of tasks after the execution plan has initially been attached.–
What I’ve found is that execution plans are still more forgiving when it comes to ad-hoc tasks than workflow is. Because of this, if you have a need for ad-hoc tasks in Change requests, I think it makes a lot of sense to split the workflow processing and use Graphical workflow for approvals, and use Execution plans for task generation. If there’s no need for ad-hoc tasks, then I think Graphical workflow works best.

The challenge with ad-hoc tasks is the sequencing of those tasks. This business rule basically re-orders the entire list of associated change tasks in the execution plan each time the order on a change task changes. This includes when a new task is inserted.

-Table: Change task
-Order: 100
-Runs after insert/update
-Condition: current.order.changes()

syncTaskOrder();

function syncTaskOrder(){
//Query for all tasks that are associated to the same parent
var rec = new GlideRecord('change_task');
rec.addQuery('parent', current.parent);
rec.orderBy('order');
rec.query();
while(rec.next()){
//Delete all of the existing successor/predecessor records for the task
var rec1 = new GlideRecord('execution_plan_local');
var qc = rec1.addQuery('predecessor', rec.sys_id);
qc.addOrCondition('successor', rec.sys_id);
rec1.query();
while(rec1.next()){
rec1.deleteRecord();
}
}

//Query for all tasks that are associated to the same parent
var tsk = new GlideRecord(current.sys_class_name);
tsk.addQuery('parent', current.parent);
tsk.orderBy('order');
tsk.query();
var lastID = '';
var lastOrder;
var myIDArray=new Array();
while(tsk.next()){
if(tsk.order > lastOrder){
//Iterate through the sys_id array and create a new successor/predecessor record for each item
for(x in myIDArray){
//Initialize the creation of a new Task Sequencing record
var tsk1 = new GlideRecord('execution_plan_local');
tsk1.initialize();
tsk1.predecessor = myIDArray[x];
tsk1.successor = tsk.sys_id;
tsk1.insert();
}
//Empty the existing array
myIDArray.length = 0;
//Populate the current task sys_id into the array
myIDArray[0] = tsk.sys_id.toString();
}
else if((tsk.order == lastOrder) || !lastOrder){
var myIDArrayLength = myIDArray.length;
if(myIDArrayLength > 0){
//Get the last item in the array
var arrayIDVal = myIDArray[myIDArrayLength - 1];
//Query the Task Sequencing table for that item
var ps = new GlideRecord('execution_plan_local');
ps.addQuery('successor', arrayIDVal);
ps.query();
while(ps.next()){
//Create a new successor/predecessor record for the current task
var ps1 = new GlideRecord('execution_plan_local');
ps1.initialize();
ps1.predecessor = ps.predecessor;
ps1.successor = tsk.sys_id;
ps1.insert();
}
}
//Populate the current task sys_id into the array
myIDArray[myIDArrayLength] = tsk.sys_id.toString();
}
else{
}
lastOrder = Math.round(tsk.order);
lastID = tsk.sys_id.toString();
}
}

I also had to include this business rule to get the orders on the tasks to sync with execution plan task orders initially (although this may have been a bug that has since been fixed so this one may be optional).

-Table: Change task
-Order: 1,100 – This is important!
-Runs before insert
-Condition: current.delivery_task != ”

current.order = current.delivery_task.order;

I suppose the above method could also be used with Service requests, but I haven’t tried it.

Option #2:

I’ve seen this option used for service requests in ServiceNow. You can set up ‘dummy’ service request items that can be added to a service request with a defined number and ordering of tasks. This method could be used with either Graphical workflow or Execution plans.

The client that used this method (like most other customers) didn’t have a defined Service catalog, but wanted to use our Service catalog anyway. Basically what they ended up doing was having a single generic Service catalog item with a couple of steps that got added to any service request. Then they allowed their technicians to add other pre-defined request items to the request on the back end using the ‘Add new item’ button on the Request form. They defined as much as they could, but they also had pre-defined items that had tasks associated with them like ‘One task item’, ‘Three task item’, etc.
The nice thing about this method is that you can still attach your workflow or execution plan directly to those items. The person who needs to add more tasks can simply add another item to the request and it already has its workflow associated with it.

Option #3:

Create sub-tasks that can be manually added to the generated change or catalog tasks. Here is an example of how you could handle this for change requests. Let’s say that all of your change requests have to go through the same 3 steps. However, in certain cases, one or more ad-hoc tasks are needed. You could set up an execution plan (or workflow) to create 3 change tasks, and then create another table called ‘subtask’ that could be manually added to each auto-generated change task as necessary like this…

*Change Request
*Change task 1 –> FROM EXECUTION PLAN
*Subtask 1 –> MANUALLY ADDED
*Subtask 2 –> MANUALLY ADDED
*Change task 2 –> FROM EXECUTION PLAN
*Change task 3 –> FROM EXECUTION PLAN

So Subtasks 1 and 2 are children of Change task 1, and change task 1 does not get closed until subtasks 1 and 2 are closed.

Option #4:

Use a script in a ‘Wait for’ activity in your workflow to query for associated tasks and wait for their completion before moving on. This approach doesn’t deal with the ordering directly, but it does allow you to control tasks that are added outside of the workflow scope. The full solution is documented here in the Service-now wiki.

The post Managing Ad-hoc Tasks in ServiceNow Workflow appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/graphical-workflow/managing-adhoc-tasks-servicenow-workflow/feed/ 8
Canceling executing workflows on task closure https://servicenowguru.com/business-rules-scripting/canceling-executing-workflows-task-closure/ https://servicenowguru.com/business-rules-scripting/canceling-executing-workflows-task-closure/#comments Tue, 29 Dec 2009 12:55:14 +0000 https://servicenowguru.wpengine.com/?p=407 It is very common to use graphical workflow to help facilitate some change management process within your organization. One common requirement in change management is to be able to cancel or close the change request at any time during the process. "Simple", you say. "Just allow the user to change the value of the 'State' field to 'Closed'."
You would not be incorrect in saying something like that, but you would be forgetting about part of the problem with closing or canceling a change request or other task ticket.

The post Canceling executing workflows on task closure appeared first on ServiceNow Guru.

]]>
Service-now.com provides a very robust and simple way to manage your tasks and approvals (among other things) through its graphical workflow engine.  It is very common to use graphical workflow to help facilitate some change management process within your organization.  One common requirement in change management is to be able to cancel or close the change request at any time during the process.  “Simple”, you say.  “Just allow the user to change the value of the ‘State’ field to ‘Closed’.”

You would not be incorrect in saying something like that, but you would be forgetting about part of the problem with closing or canceling a change request or other task ticket.  What if the attached workflow(s) still think that the change request and its associated tasks and approvals are still in progress?  Should the attached workflow context(s) continue to run indefinitely?  If your workflow doesn’t have a way to know about the completion of the change request then it will continue to run (or more likely just sit and be forgotten).


The answer to this problem is actually pretty simple.  Service-now.com comes with several out-of-box workflow utility functions defined under ‘System Definition -> Script Includes’ that can be helpful in situations like these.  While you don’t want to modify these script includes, it is probably a good idea as a Service-now administrator to become familiar with the tools and functions there.  One of the functions in the ‘Workflow’ script include is called ‘cancel’.  It can be used to cancel any running workflow activities for a given record. This script could be called from a UI action button, another workflow, or a business rule. You just need to be able to tell the function what GlideRecord should have its workflows canceled. The example below shows how you could create a business rule to cancel all running workflows for a given record if the ‘active’ field changed to ‘false’. The cancellation in the example below happens for the ‘current’ GlideRecord object (which is the current record being updated).

Cancel All Workflow Contexts for a given Record

Cancel Workflow Business Rule
Name: Cancel workflows
When: After
Insert: True
Update: True
Condition: current.active.changesTo(false)
Script:

//Query for all executing workflows and cancel any running activities
new Workflow().cancel(current);

Cancel a Single Workflow Context (by name) for a given Record

You may also encounter situations where you don’t want to cancel all associated workflow contexts, just a single one, or all but one. Again, you can find the solution in the ‘Workflow’ script include by way of the ‘getRunningFlows’ and ‘cancelContext’ functions. The following script could be run from a business rule, UI action, or even within a ‘Run Script’ workflow activity. The example given here cancels any ‘Routine Change’ workflow contexts associated with the ‘current’ record.

//Query for all executing workflow contexts
var flows = new Workflow().getRunningFlows(current);
while(flows.next()){
//Check for associated workflows by name
if(flows.workflow_version.getDisplayValue() == 'Routine Change'){
//Cancel the workflow context
new Workflow().cancelContext(flows);
}
}

The post Canceling executing workflows on task closure appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/business-rules-scripting/canceling-executing-workflows-task-closure/feed/ 23
Reset change request workflow, approvals, and tasks https://servicenowguru.com/business-rules-scripting/reset-change-request-workflow-approvals-tasks/ https://servicenowguru.com/business-rules-scripting/reset-change-request-workflow-approvals-tasks/#comments Mon, 28 Dec 2009 14:41:15 +0000 https://servicenowguru.wpengine.com/?p=381 When implementing the Change management process in Service-now you'll probably encounter a scenario where your entire change workflow (including all tasks and approvals) needs to be reset. The first option to consider (assuming you're using the graphical workflow engine to manage the tasks and approvals) is the Rollback workflow activity. The rollback activity works great for a lot of scenarios, but what if you don't have a defined point in the workflow where everything should be rolled back? What if the rollback (or reset) can happen at any point in time? There's not really an easy way to handle this within the workflow itself since you would need to check for a rollback condition at an infinite number of places.

The post Reset change request workflow, approvals, and tasks appeared first on ServiceNow Guru.

]]>
When implementing the Change management process in ServiceNow you’ll probably encounter a scenario where your entire change workflow (including all tasks and approvals) needs to be reset.  The first option to consider (assuming you’re using the graphical workflow engine to manage the tasks and approvals) is the Rollback workflow activity.  The rollback activity works great for a lot of scenarios, but what if you don’t have a defined point in the workflow where everything should be rolled back?  What if the rollback (or reset) can happen at any point in time?  There’s not really an easy way to handle this within the workflow itself since you would need to check for a rollback condition at an infinite number of places.

I recently encountered this scenario while working with a client and used the ‘SNC Approval – Reset conditions‘ business rule to solve the problem.  By using a business rule, you can allow the end user (which in this case would typically be a member of the Change Advisory Board) to indicate when a full workflow reset should be done.  Users can force a reset by setting the value of a field on the Change request ticket itself. The ‘SNC Approval – Reset conditions’ business rule gives you 3 different options for how the approval reset should happen.

1 – Cancel all existing approvals and reset
2 – Delete all existing approvals and reset
3 – Leave all existing approvals alone and reset

I chose to use option 1 so that we would have a record of any previous approval activity that had taken place. Depending on the situation and your organization’s audit requirements, option 2 might be a good alternative as well. I can’t think of a scenario where option 3 would be a good thing but it’s there if you need it.

The solution I used was to set up a UI action button that I named ‘Reset change workflow’.  The sole purpose of the UI action is to set some value on the change request ticket to trigger the ‘SNC Approval – Reset conditions’ business rule.  I chose to create a new choice value in the ‘Approval’ field called ‘Reset’.  My business rule checks before any update to see if the value of the ‘Approval’ field is ‘Reset’.  If it is, then the workflow attached to the change request gets reset.  Here’s the step-by-step guide to implement this type of reset functionality…

1Personalize the choices for the ‘Approval’ field on the change request form and add an option called ‘Reset’.

2Create a new UI action with the following parameters:

Name: Reset change workflow
Table: Change request
Client: True
OnClick: setApprovalReset()
Condition: gs.hasRole(‘itil’)
Form button: True
Script:

function setApprovalReset(){
var con = confirm('Performing this action will reset the associated workflow including all approvals. Are you sure you want to continue?');
if(con){
g_form.setValue('approval', 'Reset');
gsftSubmit(gel('sysverb_update_and_stay'));
}
else{
return con;
}
}

3Modify the ‘SNC Approval – Reset conditions’ business rule as follows:

Name: SNC Approval – Reset conditions
Table: Change request
Active: True
Condition: current.approval.changesTo(‘Reset’) && gs.hasRole(‘itil’)
Script:

// these are the conditions under which the change request approvals need to be canceled and reset
// steps to activate
// 1. enable this business rule
// 2. add some comments so the reset will be noted in the approval history
// 3. uncomment the code for restart or reset based on your requirements
// 4. define the reset condition in checkResetConditions function below
// 5. you must set doReset once you capture the change(s) you are interested in

var comment = 'Approval reset by ' + gs.getUserDisplayName(); //written to the approval_history
if (checkResetConditions()) {
// create a global variable lock on the current record
// this will prevent triggering a second reset while the first reset is still in progress
// lock will be release in a late running business rule called 'Workflow Release Lock'
if (typeof GlideRecordLock != 'undefined')
chg_wf_lock = GlideRecordLock(current);
else
chg_wf_lock = new Packages.com.glide.script.RecordLock(current);
chg_wf_lock.setSpinWait(50); //wait for lock
if (chg_wf_lock.get()) {
gs.print('SNC Approval conditions business rule is locking the ' + current.getDisplayValue() + ' during the workflow reset');

// The following options are available for resetting the approvals:
//
// 1. Mark all existing approvals for the change as 'cancelled' and restart the workflow to create new approvals
new WorkflowApprovalUtils().cancelAll(current, comment);
new Workflow().restartWorkflow(current);
//
// 2. Delete all of the existing approvals for the change and restart the workflow to create new approvals
// new WorkflowApprovalUtils().reset(current, comment);
// gs.addInfoMessage('Workflow has been reset since key fields have been modified');
//
// 3. Leave the approvals for the change in their current state and restart the workflow.
// (so that any new approvals that are required will be created)
// if (comment)
// current.setDisplayValue('approval_history', comment);
// new Workflow().restartWorkflow(current, true);
//

}

//Use this section to reset any necessary information on the Change request
current.work_notes = 'Change approvals and workflow reset due to material change by ' + gs.getUserDisplayName();
current.approval = 'not requested';

//Use this section to reset any necessary information on any associated Change tasks
var tsk = new GlideRecord('change_task');
tsk.addQuery('change_request', current.sys_id);
tsk.query();
while(tsk.next()){
tsk.work_notes = 'Change request workflow reset.';
tsk.update();
}
gs.addInfoMessage('Change approvals and workflow reset for ' + current.number + '.');
}

function checkResetConditions() {
var doReset = false; //default to no reset
//add reset conditions here such as:
//if (current.assigned_to.changes())
doReset = true; //enable the reset

return doReset;
}

The post Reset change request workflow, approvals, and tasks appeared first on ServiceNow Guru.

]]>
https://servicenowguru.com/business-rules-scripting/reset-change-request-workflow-approvals-tasks/feed/ 23