- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
07-12-2023 10:19 AM - edited 07-12-2023 10:41 AM
Initial Situation
In my current project, we have different custom applications which are deployed via the application repository. During the last deployment session I was wondering about an extreme high number of artifacts for one of the applications because none of the features contained in it could justify this high number.
So I started to investigate the app and grouped by the class first to find the candidates that have an above-average number of artifacts.
That listing was an unexpected surprise! Yes, we have custom tables with some fields in two languages. But this can never result in almost 1300 field labels! And all the artifacts that have anything to do with Flows don't make sense to me either. And since I absolutely hate it when I don't understand something, I started digging deeper. This article describes the interesting findings related to Flows from this effort and provides a solution on how to prevent your applications from being bloated with unwanted Flow artifacts.
Analysis
First of all, I wanted to understand where all the field labels are coming from. After opening the sys_documentation table, I could find tons of items with "strange" table names - starting with "var__m_"
I'm still not sure whether these tables really exists or just act as identifiers. But what you can see in the above screenshot are the real Flow-related tables within the first column like sys_hub_flow_input or sys_hub_flow_variable.
Therefore, the next step was taking a look at these tables, for example sys_hub_flow_variable:
Interesting! Also in that table you can find the "strange" names within the "Table" column and furthermore it seems that many records are referring to the same Flow variable. Okay then let's open one of these records:
Does this screenshot remind you of anything? Yes, it looks like the column definition of a table within the dictionary. But there is no reference to a table. So what is the purpose of such a record if there is no relation to any "container"?
Next step is opening the XML view of that record
Scrolling around, I came across some interesting fields starting with "model". Especially the "model_table" is of interest as this seems to be the container I was looking for. Going back to the list view with "model" columns enabled, the picture starts to become clearer
The reference to the Flow table makes sense, as this is the place where the "test_date" variable comes from. But what about all the redundant records referencing to the "Flow Snapshot" table?
I do not want to go into too much detail at this point. In simple terms, a Flow snapshot represents a compiled version of a Flow. At the end, Flows are nothing else than precompiled Java programs, and that's the reason why they are so fast.
A search on ServiceNow's support portal reveals an interesting article: https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB1117831
At the end of that article, you can find important information:
sys_hub_flow_snapshot only holds non-update-to-date flow information. That's why it's named 'snapshot' and the table has updateSync=false.
sys_hub_flow holds the published flow information. It will be captured by Update Sets
This is quite irritating! So, if the records from table sys_hub_flow_snapshot are not captured in an update set, why does the table have a sys_scope column whereby its records are captured and transported in an application? And can we really deduce from the article's statement that only the records from the sys_hub_flow table are important, since they contain all the information of a Flow?
It's time for a small test!
Reproducing the scenario
I created a "Test" application and the following small Flow within it:
After Flow activation, I opened the list of all artifacts for my test application:
You can see that even with such a simple Flow many artifacts are created in the background and except the Flow itself they are all not captured in an update set (column "Update name" is empty).
Now I was curious to see what the sys_update_xml record for the Flow contains.
The above screenshot displays only a small part of the XML payload, which is rather huge. Most interesting are the records with pure data, like the static values of my flow variables. This proves that really everything about a Flow is stored in a single record at the sys_update_xml table. Conversely, this could also mean that all Flow artifacts without a dedicated sys_update_xml record could be removed from the app.
However, instead of deleting the useless records, which is always a bad idea, I just moved them to the "Global" application. Then I published my "Test" application with the single remaining sys_hub_flow record to the application repo. After installing the "Test" app on a different instance, I opened the Flow and saw that everything looked fine. Nothing was missing. The reason for this is that ServiceNow could not find a snapshot of this Flow on the target instance and therefore compiled it instantly.
Now the question is how to prevent all the useless Flow-related artifacts from being captured in an app in the future.
Solution
In my article My collected list of useful Business Rules I included a Business Rule which "redirects" the creation of some artifact types to the "Global" scope. Based on that idea, I created a "before" Business Rules on table sys_metadata for update & insert operations which does the same for Flow-related artifacts and records of table sys_documentation which refer to any Flow-related artifacts. That Business Rule has no condition, as the complete logic is handled within the script:
(function executeRule(current) {
var _strClassName = current.getValue('sys_class_name') || '';
var _strScope = current.getValue('sys_scope') || '';
var _strUpdateName = current.getValue('sys_update_name') || '';
//do nothing if the artifact is already in Global scope or
//if there is a related record in the sys_update_xml table
if (_strScope === 'global' || _strUpdateName !== '') {
return;
}
//do nothing in case the Flow itself is inserted/updated
if (_strClassName === '' || _strClassName === 'sys_hub_flow') {
return;
}
//filter out two special tables and some candidates from the Flow area
if (_strClassName === 'sys_push_notif_app_install' ||
_strClassName === 'sys_filter' ||
_strClassName.startsWith('sys_hub_flow_') ||
_strClassName.startsWith('sys_flow_'))
{
current.setValue('sys_scope', 'global');
current.setValue('sys_package', 'global');
return;
}
// filter out field labels which are related to Flows
if (_strClassName === 'sys_documentation') {
var _strName = current.getValue('name') || '';
if (_strName.startsWith('var__m_sys_hub_flow_')) {
current.setValue('sys_scope', 'global');
current.setValue('sys_package', 'global');
}
}
})(current);
This code is a first draft version, which already works like a charm. Now, only the Flow record from table sys_hub_flow is captured in an application. For all other Flow-related artifacts, the scope is set to "Global".
- 1,752 Views
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Wow, thank you, Maik, I found a lot of answers to my questions in this article. Thank you for all your hard work in the community!
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Thank you @Renat Akhmedov for the feedback!
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@Maik Skoddow This is great, thanks! Found this article via a google search after noticing the same thing in application file lists.
Just as a note, I needed to tweak the script slightly to grab the actual sys_documentation record as otherwise the name was just coming back empty.
// filter out field labels which are related to Flows
if (_strClassName === 'sys_documentation') {
// get the sys_documentation record else the `name` field is not exposed properly
var docGr = new GlideRecord('sys_documentation');
if (docGr.get(current.sys_id)) {
var _strName = docGr.getValue('name') || '';
if (_strName.startsWith('var__m_sys_hub_flow_')) {
current.setValue('sys_scope', 'global');
current.setValue('sys_package', 'global');
}
}
}