How to calculate Salesforce field utilization with Node.js

Field utilization is, well, being able to see how much a field is used, not in the metadata, but in the data. e.g., how many records have value in this field?

Many apps exist to calculate this but let's figure out how we can do it ourselves with just a few lines of code.

So let's assume that we are passed a field variable, which contains the full field name, like Account.CustomerPriority__c.

Data structure to return

The first thing we need is a data structure to define the returned value that will help us populate a chart as shown above. This is what I used

let metrics = {
    field,
    object,
    totalRecordsPopulated:0,
    totalRecords,
    recordTypeCount:{}
 };

Then, we need to do is find out how many records exist in that object

let [object,fieldName] = field.split('.');

let restApi = restAPI(connection,logError);
let metadataApi = metadataAPI(connection,logError);

In the code above, I instantiated a client for the REST and Metadata APIs using sfdc-happy-api (but you could and maybe should use jsforce).

Then, we simply use an aggregate SOQL query to see how many records exist in the object that the field belongs. We have the total count in totalRecordsCount.

let countQuery = `select count(id) from ${object}`;
let countResult = await restApi.query({query:countQuery});
let totalRecordsCount = countResult.records[0]['expr0'];

Figuring out if the object has record types

Now, we need to check if this object has record types because if it has, we want the number of records with a field value to be broken down by record type, as shown in the picture above.

To do that, we can use the following SOQL query

let recordTypeQuery = `select developername from recordtype where SobjectType = '${object}'`;

let recordTypeResult = await restApi.query({query:recordTypeQuery});
let hasRecordTypes = recordTypeResult.records.length > 0;

Using Promise.all to query all records by record type

Now, if there are record types, we need to issue one SOQL query per record type, and see how many records meet the condition of field != null.  We can wrap all the SOQL queries inside Promise.all, that way, if the object has 10 record types, we execute all 10 SOQL queries in one go and not one by one.

 await Promise.all(

            recordTypeResult.records.map(async (recordType) => {
            
                let recordsQuery = `SELECT count(id) FROM ${object} WHERE ${fieldName} != null AND RecordType.DeveloperName = '${recordType.DeveloperName}'`;
                let recordsResult = await restApi.query({query:recordsQuery});
                let countRecordsWithValue = recordsResult.records[0]['expr0'];
    
                metrics.totalRecordsPopulated += countRecordsWithValue;
                metrics.recordTypeCount[recordType.DeveloperName] = countRecordsWithValue;
    
                return null;
            })
        );

        metrics.recordTypeCount.empty = totalRecordsCount - metrics.totalRecordsPopulated;

Very important to note that inside Promise.all we are using the map function along with async in order to return an array of functions. This is because you cannot call async inside a traditional for loop. This took me ages to figure out, so take note!

Special case for TextArea fields

Now, this would not work if the field type was TextArea because you cannot use those fields in SOQL queries. So, before we actually execute the above queries, we need to use the Metadata API's readMetadata call to find out what is the field type

let fieldDescribe = await metadataApi.readMetadata('CustomField',[field]);
let isTextArea = fieldDescribe[0].type == 'LongTextArea'

And if it is, then the query must be very different, here's what I did

let recordsQuery = `SELECT ${fieldName} FROM ${object} WHERE RecordType.DeveloperName = '${recordType.DeveloperName}' AND CreatedDate = LAST_N_MONTHS:6 ORDER BY CreatedDate Desc LIMIT 1000`;
let recordsResult = await restApi.query({query:recordsQuery});
let countRecordsWithValue = 0;

recordsResult.records.forEach(record => {
    if(record[fieldName] != null && record[fieldName] != ''){
        countRecordsWithValue++;
        localTotalRecordsWithValue++;
    }
})

Essentially I'm just getting the last 1000 records created in the past 6 months, and manually checking if the field has a value or not. This obviously gives an incomplete picture of the field population, but it's a reasonable tradeoff I think.

How utilization percentage is calculated

The percentage populated is then calculated like this

metrics.percentagePopulated = Math.round((metrics.totalRecordsPopulated*100)/metrics.totalRecords);

Finally, we can call our function in this way

let customField = 'Account.CustomerPriority__c';

let response = await getUsageMetrics(connection,customField);
console.log(response);

End result

And here's the object that we return, with all the information about how many records exist in that object, how many have a value in this field, and a breakdown by record type.

{
  field: 'Account.CustomerPriority__c',
  object: 'Account',
  totalRecordsPopulated: 36,
  totalRecords: 41,
  recordTypeCount: {
    Internal: 12,
    Partner: 15,
    Reseller: 6,
    Customer: 3,
    External_Vendor: 0,
    empty: 5
  }
}

Nice! You can see the rest of the code here.

In a future issue, I'll talk about how to get this format to be displayed nicely in the UI.

💡
If you found this article useful, please consider subscribing! I have big plans for subscribers in the future :) 

Subscribe to become a Salesforce API and CI/CD expert
fullstackdev@pro.com
Subscribe