1

I'm trying to using a scheduled Tooling API Apex class to lookup Users with a specific Role and update the picklist choices with those results.

As it is now, I'm not getting any results. In the debug logs it doesn't even look like it's running lines 4-51 and my guess is that's because of @future(callout=true).

We wanted to make it schedulable because we wanted to update it every day based on our users changing.

global class UpdatePlayerPicklistValues implements Schedulable {
    
    global void execute(SchedulableContext ctx) {
    
        updatePicklistValues();
    }
@future(callout=true)
    public static void updatePicklistValues() {
        Date yesterday = System.today().addDays(-1);
        // Query for deactivated users
        List<User> deactivatedUsers = [
            SELECT LastName, FirstName
            FROM User
            WHERE IsActive = false AND LastModifiedDate >= :yesterday AND LastModifiedDate < :System.today()
        ];

        // Query for newly activated 'Player' users
        List<User> newPlayers = [
            SELECT LastName, FirstName
            FROM User
            WHERE IsActive = true AND UserRole.Name = 'Manager' //AND CreatedDate >= :yesterday AND CreatedDate < :System.today()
        ];

        updatePicklistField(deactivatedUsers, newPlayers);
    }
//@future(callout=true)
    private static void updatePicklistField(List<User> deactivatedUsers, List<User> newPlayers) {
        // Setup HTTP request to Tooling API        
        HttpRequest req = new HttpRequest();
        req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
        req.setHeader('Content-Type', 'application/json');
        req.setEndpoint(URL.getOrgDomainURL().toExternalForm() + '/services/data/v60.0/tooling/sobjects/CustomField/00N8L000000vB8yUAE');        
        req.setMethod('PATCH');

        // Construct the metadata changes for deactivation and addition
        List<Map<String, Object>> picklistValues = new List<Map<String, Object>>();
       
        for (User u : deactivatedUsers) {
            picklistValues.add(new Map<String, Object>{
                'isActive' => false,
                'value' => u.LastName + ', ' + u.FirstName
            });
        }

        for (User u : newPlayers) {
            picklistValues.add(new Map<String, Object>{
                'isActive' => true,
                'value' => u.LastName + ', ' + u.FirstName,
                'default' => false
            });
        }

        //picklist metadata

        Map<String, Object> value = new Map<String, Object>{
            'value' => picklistValues
                };
        Map<String, Object> valueSetDefinition = new Map<String, Object>{
            'valueSetDefinition' => value
            };
        //label...
        Map<String, Object> valueSet = new Map<String, Object>{
            'label' => 'YourPicklistLabel',
            'type' => 'Picklist',
            'valueSet' => valueSetDefinition
            };
        //FullName...
        Map<String, Object> fieldMetadata = new Map<String, Object>{
            'Metadata' => valueSet,
            'FullName' => 'ObjectAPIName.FieldAPIName'
            };

        req.setBody(JSON.serialize(fieldMetadata));
       
        Http http = new Http();
        HttpResponse res = http.send(req);
       
        if (res.getStatusCode() != 200) {
            System.debug('Error from Tooling API: ' + res.getBody());
        }
    }
}
    

If you can give any insight I would really appreciate it. I posted this before but I messed up the post so I had to re-post. I apologize for that. Thank you very much.

1
  • 2
    The future method itself will create a separate debug log. You'll have to find that log separately.
    – sfdcfox
    Commented May 8 at 2:00

1 Answer 1

2

I've copied your code into my scratch org and executed it from Execute Anonymous:

UpdateBankerPicklistValues schedulable = new UpdateBankerPicklistValues();
schedulable.execute(null);

As sfdcfox points out - and it's easy to overlook: The log written by the future transaction is not the same as the one starting it all. The future log had this:

... DEBUG|Error from Tooling API: [{"message":"Cannot deserialize instance of complexvalue from START_ARRAY value [line:1, column:32]","errorCode":"JSON_PARSER_ERROR"}]

I didn't find a literal Salesforce documentation of updating picklists, but Ayub's answer on StackExchange has an example.

The tree structure is deeper than in the Apex code presented here. The request also lacks some required fields. Worst of all for us developers: If we do not list a picklist entry, it will be deactivated. So make sure you get and add them all to the JSON. Here is the object hierarchy with the necessary depth and required fields, but not all users ;-) :

[...]
for (User u : deactivatedUsers) {
    picklistValues.add(new Map<String, Object>{
        'isActive' => false,
        'valueName' => u.LastName + ', ' + u.FirstName
    });
}

for (User u : newBankers) {
    picklistValues.add(new Map<String, Object>{
        'isActive' => true,
        'valueName' => u.LastName + ', ' + u.FirstName,
        'default' => false
    });
}

// TODO: Make sure you got all picklist values

Map<String, Object> value = new Map<String, Object>{
    'value' => picklistValues
};
Map<String, Object> valueSetDefinition = new Map<String, Object>{
    'valueSetDefinition' => value
};
// TODO: adjust the label...
Map<String, Object> valueSet = new Map<String, Object>{
    'label' => 'YourPicklistLabel',
    'type' => 'Picklist',
    'valueSet' => valueSetDefinition
};
// TODO: adjust the FullName...
Map<String, Object> fieldMetadata = new Map<String, Object>{
    'Metadata' => valueSet,
    'FullName' => 'ObjectAPIName.FieldAPIName'
};
[...]
8
  • -Thank you very much for your help with this. I have applied your suggestions and they have definitely helped. I put them in my original post.Now getting that error during the callout after it has successfully built a list : "Error from Tooling API : "Cannot deserialize instance of comlexvalue from VALUE_STRING value..." you mentioned that. Looking into that with your reference. Thank you again.
    – Zoom_v
    Commented May 8 at 17:58
  • Please don't modify your original question in a way that it makes it impossible for later readers to understand what the problem was and learn from it. As for the error you get - you need to correct the label and the Fullname - have you done this? Commented May 8 at 18:12
  • I apologize for changing the original post. I only thought it was helping to include your suggestions. Yes, I have included my proper field labels & API object/field names in my actual code. But I'm getting the 'Cannot deserialize instance of complexvalue...' error in the callout with the JSON parser. I saw in the article you referred to that somebody else mentioned the deserialize error and said they fixed it with "valueSettings":[] instead of null but I don't refer to valueSettings at all so I don't know if that applies. Or do I need to specifically refer to [] ?How would I do that?
    – Zoom_v
    Commented May 8 at 20:20
  • 1
    No, you don't need all these fields. The attributes I listed (in the Maps) are enough - I tested it, this worked on my scratch org. Do a System.debug(JSON.serialize(fieldMetadata)) and make sure it has exactly the same structure like the one that you see in the Ayub's answer. Commented May 8 at 21:26
  • 1
    Unfortunately, I now have a new problem with Record Type visibility of choices. It's not a problem when there are no Record Types. I'm gonna just turn this into a new question on here. I don't want to ruin the history of this one.
    – Zoom_v
    Commented May 9 at 18:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .