From 5f0d87e044f977c5d26070aa7ed84034d1eed609 Mon Sep 17 00:00:00 2001 From: pgonzaleznetwork Date: Thu, 1 Oct 2020 10:39:56 +0100 Subject: [PATCH] we now tell if a field is used for assignment or reading;new video uploaded to readme and login page (#36) --- README.md | 7 +++++- public/css/main.css | 20 +++++++++++++++ public/index.html | 2 +- public/js/tree.js | 11 +++++++++ sfdc_apis/usage.js | 60 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e101a39..3ebd4f6 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,15 @@ No **development knowledge** required :x: Just [log in](https://sfdc-happy-soup.herokuapp.com/) and start sipping the soup! :stew: :clap: :white_check_mark: +[Watch full demo](https://www.youtube.com/watch?v=2asljhebqlY&t=6s)

-[Watch full demo](https://www.youtube.com/watch?v=NH4LGbdYaaE) + + + ## Contents @@ -191,6 +194,8 @@ Salesforce Happy Soup is built on top of the `MetadataComponentDependency` tooli * Lookup filters are returned with cryptic names depending on whether they belong to a custom object or a standard one. +* The app will tell you if a field is used in an apex class in read or write mode. For example, if a field is used in an assignment expression, then you know the class is assigning values to that field. The app will show you this with a visual indicator; something that the raw API cannot do. + As said above, Salesforce Happy Soup has **fixed all** this issues so that you can focus on learning about your dependencies rather than fighting the API! :facepunch: [Back to top](#salesforce-happy-soup) diff --git a/public/css/main.css b/public/css/main.css index c80caaa..e80b8e7 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -455,4 +455,24 @@ select { .metadata-info .prop{ font-weight: bold; +} + + +.class-info{ + border: 1px solid white; + border-radius: 50px; + padding-left: 5px; + padding-right: 5px; + margin-left: 5px; + padding-top: 0px; + padding-bottom: 1px; + font-size: 11px; +} + +.write{ + background-color: #d63031; +} + +.read{ + background-color: #49b677; } \ No newline at end of file diff --git a/public/index.html b/public/index.html index 28269c2..95fb863 100644 --- a/public/index.html +++ b/public/index.html @@ -60,7 +60,7 @@

Happy Soup

-
diff --git a/public/js/tree.js b/public/js/tree.js index cafc3ca..9743f73 100644 --- a/public/js/tree.js +++ b/public/js/tree.js @@ -80,6 +80,10 @@ function createTreeNodes(refs,parentNode){ if(member.namespace){ memberNodeName.appendChild(createWarningIcon()); } + + if(member.fieldMode){ + memberNodeName.appendChild(createPill(member.fieldMode)); + } memberNames.appendChild(memberNodeName); metadataTypeNode.appendChild(memberNames); @@ -102,6 +106,13 @@ function createTreeNodes(refs,parentNode){ return warningIcon; } + function createPill(text){ + let pill = document.createElement('span'); + pill.classList.add(...['class-info',text]); + pill.innerText = text; + return pill; + } + export const treeApi = { createDependencyTree,createUsageTree }; \ No newline at end of file diff --git a/sfdc_apis/usage.js b/sfdc_apis/usage.js index 52d1a69..49803d8 100644 --- a/sfdc_apis/usage.js +++ b/sfdc_apis/usage.js @@ -64,6 +64,7 @@ function usageApi(connection,entryPoint,cache){ let customFields = []; let layouts = []; let lookupFilters = []; + let apexClasses = []; let otherMetadata = []; metadataArray.forEach(metadata => { @@ -84,6 +85,9 @@ function usageApi(connection,entryPoint,cache){ else if(type == 'LOOKUPFILTER'){ lookupFilters.push(metadata); } + else if(type == 'APEXCLASS' && entryPoint.type == 'CustomField'){ + apexClasses.push(metadata); + } else{ otherMetadata.push(metadata); } @@ -101,16 +105,72 @@ function usageApi(connection,entryPoint,cache){ if(lookupFilters.length){ lookupFilters = await getLookupFilterDetails(lookupFilters); } + if(apexClasses.length){ + apexClasses = await getFieldUsageMode(apexClasses); + } otherMetadata.push(...customFields); otherMetadata.push(...validationRules); otherMetadata.push(...layouts); otherMetadata.push(...lookupFilters); + otherMetadata.push(...apexClasses); return otherMetadata; } + /** + * We know that the field is referenced in this class, but is it + * used for reading or assignment? We determine that here by checking + * if the field is used in an assignment expression in the body of the class + */ + async function getFieldUsageMode(apexClasses){ + + //i.e field_name__c without the object prefix + let refCustomField = entryPoint.name.split('.')[1]; + + /** + * This matches on custom_field__c = but it does NOT match + * on custom_field__c == because the latter is a boolean exp + * and we are searching for assignment expressions + * gi means global and case insensitive search + */ + let assignmentExp = new RegExp(`${refCustomField}=(?!=)`,'gi'); + + let ids = apexClasses.map(ac => ac.id); + ids = utils.filterableId(ids); + + let query = `SELECT Id,Name,Body FROM ApexClass WHERE Id IN ('${ids}')`; + let soqlQuery = {query,filterById:true}; + + let results = await toolingApi.query(soqlQuery); + + let classBodyById = new Map(); + + results.records.forEach(rec => { + classBodyById.set(rec.Id,rec.Body); + }); + + + apexClasses.forEach(ac => { + + //by default we assume that the mode is read only + ac.fieldMode = 'read'; + + let body = classBodyById.get(ac.id); + if(body){ + //remove all white space/new lines + body = body.replace(/\s/g,''); + + if(body.match(assignmentExp)){ + ac.fieldMode = 'write'; + } + } + }); + + return apexClasses; + } + async function getLookupFilterDetails(lookupFilters){ let metadataRecordToEntityMap = new Map();