Search code examples
salesforceapexsalesforce-lightningsoql

SalesForce query returns results in Query Editor, but returns null from APEX code in Lightning component


I'm completely new to SalesForce and have inherited a report that's not working. Please excuse any incorrect terminology, since I'm learning about all this as I go. The report has three prompts: states, years, and members. All dropdowns are supposed to populate with data returned from functions in an APEX class. State, which populates from a picklist, and years, which is populated with a loop, work fine. Members, which populates from a SQL query, returns nothing. If I run the report without any prompts selected (which should return an unfiltered list of results from a SQL query), it also returns nothing. Both of the SQL queries return data when I execute them directly in the query editor in the developer console, but they return nothing when called from the APEX functions.

Here's the initialization code from the Lightning controller:

doInit: function (component, event, helper) {
        var action = component.get('c.getTrcAccounts');
        action.setCallback(this, function (response) {
            var state = response.getState();
            if (state === 'SUCCESS' && component.isValid()) {
                component.set('v.trcAccList', response.getReturnValue());
            }
            helper.getLocationState(component, event);
            helper.getYear(component, event);
        });
        $A.enqueueAction(action);
    },

Here are the two helper functions referenced in that code:

getLocationState: function (component, event) {
        var action = component.get('c.getLocationState');
        action.setCallback(this, function (response) {
            var state = response.getState();
            if (state === 'SUCCESS') {
                component.set('v.LocationStateList', response.getReturnValue());
            }
        });
        $A.enqueueAction(action);
    },
    getYear: function (component, event) {
        var action = component.get('c.yearsOptions');
        action.setCallback(this, function (response) {
            var state = response.getState();
            if (state === 'SUCCESS') {
                component.set('v.LocationYearList', response.getReturnValue());
            }
        });
        $A.enqueueAction(action);
    }

Here is the code from the APEX class that returns the data for those three prompts:

Global class DataTableLocations {
@AuraEnabled
    Global static List<TRC_Account__c> getTrcAccounts(){
        set<string> trcAccountSet = new set<string>();
        List<TRC_Account__c> traccList = new List<TRC_Account__c>();
        for(TRC_Account__c trcacc : [SELECT Id, Name from TRC_Account__c WHERE TRC_Member__c = True order by Name limit 50000]){
            if(!trcAccountSet.contains(trcacc.Name)){
                trcAccountSet.add(trcacc.Name);
                traccList.add(trcacc);
            }
        }
        if(traccList.size()>0){
            return traccList;
        }
        else{
            return null;
        }   
    }
    @AuraEnabled
    Global static List<string> getLocationState(){
        List<string> options = new List<string>();
        //options.add(new SelectOption('SelectAll', 'Select All'));
        for( Schema.PicklistEntry f : Location__c.Physical_Address_State__c.getDescribe().getPicklistValues()) {
            options.add(f.getValue());
        } 
        return options;
    }
    
    @AuraEnabled
    Global static List<string> yearsOptions() {
        List<string> options = new List<string>();
        date OldDate= date.today().addYears(-18);
        integer oldyear=OldDate.year();
        for( integer i=0; i<19 ;i++) {
            options.add(string.valueOf(oldyear));
            oldyear++;
        } 
        return options;
    }
}

If I run SELECT Id, Name from TRC_Account__c WHERE TRC_Member__c = True order by Name limit 50000 directly in the query editor window in the developer console, I get 7 results. However, if I output the response.getReturnValue() for getTrcAccounts in the doInit function, it's null.

Any help is greatly appreciated, as we're in a bit of a time crunch in conjunction with a site redesign. I'm told these reports were working at one point, but no one knows when they stopped working, and we inherited this code from a different company that did the original development. Thank you!

UPDATE:

In case it helps, this is the code in the lightning app that I think is used on the public page:

<aura:application extends="ltng:outApp" access="GLOBAL" implements="ltng:allowGuestAccess">
    <aura:dependency resource="c:SearchBinReceiptsByYear"/>
</aura:application>

Solution

  • Edit

    Right, it's a public page, it's called "Salesforce Sites". It's exposed to whole world without having to log in. These have special security in place because most of the time you don't want to expose data like that. At best you'd display contact us form, maybe some documents to download, product catalog... It's all very locked down, default is to ban everything and then admin decides what's allowed. It's bit unusual to have a Visualforce page + Aura component but ok, it happens.

    You (and any other internal user) can see the results if you'd access this page from within salesforce. Something like https://mydomain.my.salesforce.com/apex/SearchBinReceiptsByYear and for you the page will work fine, "just" not outside of salesforce.

    When exposed like that on the web - there's no logged in user. There's special "[Site Name] Guest User", you can see them if you search "Sites" in Setup. It has a special profile, also with [Site Name] in it. And nasty thing is - it doesn't show on the list of Users or Profiles.

    Your code broke when Salesforce (auto)activated a critical update. Probably this one: https://releasenotes.docs.salesforce.com/en-us/spring20/release-notes/rn_networks_secure_perms_guests.htm There are some good resources on the net if you Google "Secure Object Permissions for Guest Users", for example https://katiekodes.com/salesforce-spring-20-guest-user/

    Ask your system administrator colleague or read up a bit about sharing rules.

    You'll have to go to Setup -> Sharing Rules. There's a checkbox that caused your stuff to break and you can't untick it.

    enter image description here

    Scroll down to your TRC Account object and hit "New". You'll need to create something like this, but with your criteria (TRC Member equals true)

    enter image description here

    Save, wait a bit (it might take a while to recalculate the sharing, you'll get an email) and try the page.

    If it still doesn't work you'll have to check the Guest user's profile, it might need permissions to Read TRC Accounts and their Name field.

    If it's Salesforce Sites - try this to find it: https://help.salesforce.com/articleView?id=000334554&type=1&mode=1

    If it's a Customer Portal, Community, Digital Experience (they renamed the product few times) - try with https://help.salesforce.com/articleView?id=sf.rss_config_guest_user_profile.htm&type=5


    Original answer

    It looks like it's running OK because accounts (members?) are fetched first and in that fetch's callback (what to do when data comes back from server) you have helper.getLocationState, helper.getYear. And you wrote that these populate OK. It's not the best performance code but it should get the job done.

    In no specific order...

    Does the whole thing work OK for sysadmins? Or is it broken for everybody? If it works for sysadmins it might be something to do with sharing, your sysadmin should know (Setup -> Sharing settings is where you control who can see what. Maybe "mortals" are not allowed to see any data? Typically sysadmins bypass it. As a quick & dirty test you can modify the class definition to global without sharing class DataTableLocations but it's a really ugly hack.

    What happens if you open DeveloperConsole (upper right corner) while running this component, do you see any errors in the logs? What happens if in the console you go Debug -> Open ExecuteAnonymous and run this piece of code:

    System.debug(DataTableLocations.getTrcAccounts());
    

    enter image description here

    Does it return something? Throw error?

    You can go to Setup -> Debug Mode, tick the checkbox next to your user and save. This slows the system down a bit but lets you debug the javascript better. You can then sprinkle some debugger; or console.log statements in the source code and view what happens in your browser's console (Ctrl+Shift+J in Chrome, Ctrl+Shift+I in firefox). For example

    action.setCallback(this, function (response) {
        var state = response.getState();
        debugger;
        console.log(state);
        console.log(component.isValid());
        console.table(response.getReturnValue());
        if (state === 'SUCCESS' && component.isValid()) {
            component.set('v.trcAccList', response.getReturnValue());
        }
        console.log(component.get('v.trcAccList'));
        debugger;
        helper.getLocationState(component, event);
        helper.getYear(component, event);
    });
    

    How's the trcAccList variable actually used in the "cmp" file, in the HTML-like file? Maybe it's being set all right and contains 7 records but it's not displayed right?