Thursday, December 19, 2019

Architect: Investigating Static Resources

Per Org, Salesforce allocates upto 250MB storage space. If you happen to run into a situation where you want to find how much storage space is being used then you could use the following 2 queries
and take action appropriately.

Following 
select id,bodylength,cachecontrol,contenttype,createdby.alias,createddate,description, lastmodifieddate from staticresource
will give you a list of all the static resources and the space it consumes

and the 
select sum(bodylength) from staticresource gives you the total storage space consumed

You might run into this issue if you have many installed packages in your system or you are using 3rd party tools like Skuid which masks Salesforce UI but allowing you to use custom UI but in the form of static resources

Enjoy !!!

Tuesday, October 8, 2019

Deleting Debug logs

If you have had to take over a system with little to KT and you have been running debug logs then you can attest to the pain points of deleting the logs by clicking Delete All..

There is a way out using Tooling API.

Delete logs are found from workBench using the following query

SELECT Id, StartTime, LogUserId, LogLength, Location FROM ApexLog

  1. Open Developer Console.
  2. At the bottom of the console, select the Query Editor tab.
  3. Select Use Tooling API.
  4. Enter this SOQL query:
    SELECT Id, StartTime, LogUserId, LogLength, Location FROM ApexLog
  5. Click Execute.
  6. Select the logs you want to delete. To sort by a column, click its header. To select individual logs, press Ctrl (Windows or Linux) or Command (macOS). To select a block of logs, press Shift.

Query to find fields being History tracked in Salesforce

Use the following query to find fields being history tracked

SELECT QualifiedApiName FROM FieldDefinition WHERE EntityDefinition.QualifiedApiName = 'Account' AND IsFieldHistoryTracked = true

You could always put this in a loop and get list of fields tracked for all objects

Monday, February 25, 2019

Executing classes from APEX Classes and APEX Test Execution....Analyzing the results....

Every few months, critical updates are released by Salesforce. As an architect, I needed to review and make sure that these updates won't have an adverse impact to our system.

If your system has a lot of custom code/development and lot of Installed Packages then you need to make sure that the system works correctly even after the updates are auto activated.


Go to APEX classes and hit the Run All test classes, this executes tests from Managed Packages as well.

The other option is to go to APEX test execution and selectively execute classes by specifying the namespace.

The critical thing after this steps is to be able to dissect the results generated as a result of the run.

Here are some queries to execute from WorkBench in BULK CSV mode.

SELECT AsyncApexJobId,Status,UserId FROM ApexTestRunResult
   SELECT ApexClassId,ApexLogId,ApexTestRunResultId,AsyncApexJobId,Id,Message,MethodName,Outcome,QueueItemId,RunTime,StackTrace,SystemModstamp,TestTimestamp FROM ApexTestResult where Outcome='Fail'
   SELECT ApexClassId,ApexLogId,ApexTestRunResultId,AsyncApexJobId,Id,Message,MethodName,Outcome,QueueItemId,RunTime,StackTrace,SystemModstamp,TestTimestamp FROM ApexTestResult where Outcome='Fail' and
   AsyncApexJobId='707S000000qe6mZIAQ'
   SELECT count(Id) FROM ApexTestResult where 
   AsyncApexJobId='707S000000qQmb7IAC' group by outcome
 
   SELECT count(Id) FROM ApexTestResult where 
   AsyncApexJobId=' ' group by outcome


When the tests are executed, an entry is created in APEX Test history indicating a TestRunID.
Drill down on the link and on the right you can see the Classes that have failed/passed.
Limitation is you can only view few at a time and can't see the details.

To get details, copy the RunID from the left and use it in the queries above, download the generated file from WorkBench and analyse the report.

I do multiple runs to make sure before/after update activation scenarios.

Tooling API

For most part I have used metadata API but never had the need to explore Tooling API aspect of Salesforce.  If you need to develop tools on metadata information from Salesforce this is the API to use.

We had a vendor move a lot of development to Production, the code coverage thru the Salesforce tools only indicated 22% code coverage while the vendor claimed to have 85% coverage.

I wondered if there was a way to crosscheck without to do a RunAll and that's where Tooling API came in handy.

Limitations: You can't use the queries in the normal sense from developer workbench; you have to go thru the RestExplorer approach.

However, you can use it in Developer console but then you can't export the data without having to do some hacks/

The Tooling API link is https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/intro_api_tooling.htm

Here are 2 situations where I needed to use:
a) Find code coverage when Salesforce and vendor claims were different
b) After a RunAll what is the code coverage for every single class in your system.

For a) run the following from WorkBench--utilities--RestExlorer

/services/data/v44.0/tooling/query/?q="SELECT+ApexClassOrTrigger.Name+NumLinesCovered+ NumLinesUncovered+FROM+ApexCodeCoverageAggregate"

Ther results will be in JSON format.
Take the response and paste in a JSON translator and you should be able to view and download the
revised output for a better understanding

OR

from developer console run the query, pasted a sample below.

SELECT ApexClassOrTrigger.Name, NumLinesCovered, NumLinesUncovered 
FROM ApexCodeCoverageAggregate 
WHERE ApexClassOrTrigger.Name = <Name>

SELECT PercentCovered 
FROM ApexOrgWideCoverage

SELECT NumLinesCovered, NumLinesUncovered 
FROM ApexCodeCoverage 
WHERE ApexClassOrTriggerId = <ID>

SELECT ApexClassOrTriggerId, ApexClassOrTrigger.Name, NumLinesCovered, NumLinesUncovered 
FROM ApexCodeCoverageAggregate 
WHERE ApexClassOrTriggerId != NULL AND ApexClassOrTrigger.Name != NULL 
AND (NumLinesCovered &gt; 0 OR NumLinesUncovered &gt; 0) AND NumLinesCovered != NULL 
AND NumLinesUncovered != NULL ORDER BY ApexClassOrTrigger.Name

SELECT ApexClassOrTriggerId, ApexClassOrTrigger.Name, NumLinesCovered, NumLinesUncovered 
FROM ApexCodeCoverageAggregate 
ORDER BY ApexClassOrTrigger.Name

Thursday, January 24, 2019

DemandTools and Salesforce....Login Setup Isses

Got ready to use a new tool called DemandTool but could not get past the Login page.

It had 2 option a) API login and b) OAuth login.

Going with the API Login, tried using my UID/PWD and just could not get thru.

I found that the URL needs to be the URL found in the salesforce Partner WSDL file.

To generate the Partner WSDL file go to Salesforce Setup/API --Generate Partner WSDL.
Open the file and look for section which has soap:address location.

Copy the URL.

Thursday, January 17, 2019

Migrating Chatter data from one Org to Another....

Had a requirement to migrate Chatter data since we are migrating our Backlog Tool to Agile Accelerator in Org B from a Salesforce based vendor tool which resided in Org A.

Typically, for custom objects, if feed tracking is on, <object>_feed will be the object under which feed related records will be found.
Standard Objects have the feed in objects like AccountFeed, CaseFeed and so on.

Data can be exported using Dataloader.

To import, for custom objects, you need to import it back into an object called FeedItem and that should allow you to see the imported records.

For standard objects, you can import it back into it's own object i.e. AccountFeed, CaseFeed and so on.

Wednesday, January 16, 2019

Reporting Errors...

Team ran into the following issue while building reports:

a) Error: This report can no longer be edited or run. Your administrator has disabled all reports for the custom object, or its relationships have changed.

b) The filter logic references an undefined filter

On reviewing the setup, it was found that users were not having read access to the object, once the read access was provided that resolved error a.

The second error b) was resolved by providing field access to all the fields on the report.



Wednesday, January 2, 2019

SSO Integration: Salesforce Azure setup-Writing UserRegistration Class

There are 2 ways of integrating Salesforece and Azure.

One way seems to be from the Azure side, please see link below

https://docs.microsoft.com/en-us/azure/active-directory/saas-apps/salesforce-tutorial

This also does automatic user provisioning.

The other way is from the Salesforce side, please see link below for the steps:

https://help.salesforce.com/articleView?id=sso_provider_azure.htm

One of the steps is to write a user registration class. Follow the steps to create a sample class, once the sample class is created you need to modify it per your needs and depending on the data provided by the SSO provider in this case Azure.

//TODO:This autogenerated class includes the basics for a Registration
//Handler class. You will need to customize it to ensure it meets your needs and
//the data provided by the third party.


global class AzureSSOUsrCheck implements Auth.RegistrationHandler{
 
       static global boolean canCreateUser(Auth.UserData data) {
         system.debug('canCreateUser');
          system.debug('canCreateUser-identifier' + data.identifier);
           system.debug('canCreateUser-username' + data.Username);
     
              //Check if User exists - if not then create user else update user
              List<User> U = [SELECT Id, Username FROM User where Email=:data.email and IsActive=true LIMIT 1];
               if (U.size() > 0 ){
                   system.debug('canCreateUser-false');
                   return false;
               }
            system.debug('canCreateUser-true');
                   return true;
           
             
         }
 
   static global User createUser(Id portalId, Auth.UserData data){
            system.debug('createUser-data.email' + data.email );
            system.debug('createUser-data.firstName' + data.lastName + data.firstName );
            system.debug('createUser-data.username' + data.username );
            if(!canCreateUser(data)) {
    //Returning null or throwing an exception fails the SSO flow
    system.debug('Returning NULL from createUser' );
                User u1 = [select Id from User where email=:data.email and IsActive=true LIMIT 1];
     return u1;
  }

     
        //User u = new User();
            //Profile p = [SELECT Id FROM profile WHERE name='Standard User'];
            //u.username = data.username + '@hexagon.com' + '.slsmktg';
            //u.email = data.email;
            //u.username = 'deepak.balur@hexagon.com.slsmktg';

        List<User> UsrVal = [select Id from User where email=:data.email and IsActive=true LIMIT 1];
     
           if (UsrVal.size() > 0){
               return UsrVal[0];
           } else {
               return null;
           }
         

        }

       static global void updateUser(Id userId, Id portalId, Auth.UserData data){
    system.debug('updateUser-data.username' + data.username + data.email );
            system.debug('updateUser-data.firstName' + data.lastName + data.firstName );
            system.debug('updateUser-data.username' + data.username );
         
            User u = new User(id=userId);
            //u.username = data.username + '@hexagon.com';
            u.email = data.email;
            u.lastName = data.lastName;
            u.firstName = data.firstName;
            String alias = data.username;

            update(u);
        }
}

Execution Flow:

When the instance URL is typed in user is presented with the option of logging in via Azure SSO credentials, once the credentials are entered the createUser method is executed.
If the user does not exist then an error is returned to the user.

If the user exists then the updateUser will be executed and fields updated as specified in the method.

Please note; the requirement was not to create users on the fly and hence the createUser method only looks if the user exists or not.

Auth.UserData is a salesforce provided class; it contains the following fields
String identifier, String firstName, String lastName, String fullName, String email, String link,String userName, String locale, String provider, String siteLoginUrl, Map<String,String> attributeMap

When the interaction happens with Azure these fields can be passed from Azure.
For this integration, I was not the Azure Admin.

Hope this helps.