• Claire Rook
  • NEWBIE
  • 30 Points
  • Member since 2014


  • Chatter
    Feed
  • 0
    Best Answers
  • 2
    Likes Received
  • 3
    Likes Given
  • 5
    Questions
  • 5
    Replies
I have a Process Builder that runs when a task is created. The idea is that it will update the WhoID. It checks the WhatID to see if it starts with a particular 3 characters that relate to a custom object.  If it does then it triggers a flow.  It sends the TaskID and the custom object ID to the flow.  The flow uses the custom object ID to find the related contact ID.  It sends me an email with the TaskID, Contact ID and Custom Object ID for troubleshooting.  Then it does a record update to update the WhoID field on the task with the Contact ID.  This works fine when the task record is created by another process.  If I create the task manually, the process fires, the flow sends me an email with all the correct IDs but the WhoID is not updated.  I added a text field on the task object and tried updaing that with the ContactID and it works in every scenario.  I have also made the launch of the flow a scheduled action so that it fires an hour after the task has been created but that does not work either.  
I have posted this in the success community and got some help there but so far the issue is not solved.  Thank you in advance for your assistance.
I've built the variable Account Revenue according to the instructions but when I verify the step I get this error "Variable 'AccountRevenue' isn't properly configured. Check the instructions."  Here's a screen shot of what my variable looks like.  Please can you help me find my mistake?  Thanks!
User-added image
I'm trying to write test code for a class that is invoked by a custom button.  This is the first time I've done this and lots of googling has not provided me with the answer!

Thank you very much for your help on this!

This is the button:
/apex/SendAdverseEvents?id={!Adverse_Event__c.Id}

I have successfully created an Adverse Event in my test code but and I think I need to instantiate the SendAdverseEventsController but I'm not sure how to do this.

Here is my test code so far:
@isTest
public class SendAdverseEventsTest {
  //Setup test data
  static testMethod void SendAdverseEvtTest() {
        
    Id rtPA = Schema.SObjectType.Account.getRecordTypeInfosByName().get('Person Account').getRecordTypeId();
  
    //create new account for testing       
    Account a = New Account ();
           a.FirstName = 'Robert';
           a.LastName = 'Smith';
           a.RecordTypeId = rtPA;
           a.Email__c = 'test@example.com';
           a.Gender__c = 'Male';
           Insert a;
    

   //find contact ID of the new account
     account RS = [Select PersonContactID from Account where LastName = 'Smith'];
    
    //create adverse event
    Adverse_Event__c AE = New Adverse_Event__c();
        AE.Patient_Identifier__c = RS.PersonContactId;
        AE.Does_the_patient_give_permission_to_call__c = 'Yes';
            AE.Does_the_patient_give_permission_to_cont__c = 'Yes';
          AE.Suspect_Drug_1__c = 'Suspect Drug 1';
          AE.Brand__c = 'Harvoni';
          AE.Is_Patient_Affiliated_with_an_Ambassador__c = 'No';
          Insert AE;
        system.debug(AE);
       
        //find ID of the new adverse event
       Adverse_Event__c AECreated = [Select ID, Name, Auto_Initials__c, Suspect_Drug_1__c, Document_File_Name__c, Document_File_Name_Gilead__c, CreatedDate from Adverse_Event__c where Patient_Identifier__r.LastName = 'Smith'];
    system.debug(AECreated);
        system.debug(AECreated.id);
    
    
    //test.startTest();
      //SendAdverseEventsController con = new SendAdverseEventsController(); // Create instance of controller class
      //con.adverseEvent.id = AECreated.Id; // Set the Id
      //con.SendAdverseEventsController(); // Call the class method, before calling method, you can populate the fields, which are require in this method
    //test.stopTest();
  }
}
    
The commented code at the end I copied from developer forums but I can't work out how to get it to work.

The SendAdverseEventsController is this:

public with sharing class SendAdverseEventsController extends BaseClass {
    
    public Adverse_Event__c adverseEvent { get; set; }
    public String toAddresses { get; set; }
    public String toContacts { get; set; }
    public String ccAddresses { get; set; }
    public String ccContacts { get; set; }
    public String bccAddresses { get; set; }
    public String bccContacts { get; set; }
    public String documentTemplateId { get; set; }
    public String subject { get; set; }
    public String body { get; set; } 
    public User contactLookup {get; set;}
    public Boolean hasAccess { get; set; }
    
    public static final String GLOBAL_EMAIL_NAME = 'AE Reports';
    public static final String GLOBAL_EMAIL = 'aereports@snow-companies.com';
    
    public String defaultBccEmail {get;set;}
    
    private Map<Id,String> documentTemplates;
    private Map<String,String> contactNames;
    
    
    public SendAdverseEventsController() {
        
        this.defaultBccEmail = GLOBAL_EMAIL;
        this.bccAddresses = GLOBAL_EMAIL;
        
        contactLookup = new User();

        Map<String,String> params = ApexPages.currentPage().getParameters();

        for (Adverse_Event__c ae : [select Name, Auto_Initials__c, Suspect_Drug_1__c, Document_File_Name__c, Document_File_Name_Gilead__c, CreatedDate from Adverse_Event__c where Id = :params.get('id')]) {
            adverseEvent = ae;  
        }
        
        for (EmailTemplate et : [select Subject, HtmlValue from EmailTemplate 
                                  where Folder.Name = 'Adverse Events Emails' 
                                    and Name = 'Email Template']) {
            //subject = et.Subject;
            subject = adverseEvent.Document_File_Name__c;
            body = et.HtmlValue;
        }

        String username = UserInfo.getUserName().split('@')[0];

        if (ApexPages.currentPage().getParameters().get('signature') != null) {
            username = ApexPages.currentPage().getParameters().get('signature');
        }

        for (EmailTemplate et : [select Subject, HtmlValue from EmailTemplate 
                                  where Folder.Name = 'Signatures' 
                                    and Name = :username]) {
            
            if (body == null) {
                body = '';
            }
            
            body += et.HtmlValue;
        }
        List<Adverse_Events_Access__c> access = Adverse_Events_Access__c.getAll().values();
        hasAccess = false;
        for(Adverse_Events_Access__c aea : access){
            if(aea.Username__c == UserInfo.getUserName()){
                hasAccess = true;
                break;
            }
        }
    }
    
    public String getGileadSubject(Adverse_Event__c  ae){
       String dateString = ae.CreatedDate.format('ddMMMyyyy').toUpperCase();
       String subjectFormatted = dateString + '-' + ae.Auto_Initials__c + '-' + ae.Suspect_Drug_1__c + '-' + ae.Name;
       
       return subjectFormatted;
   }
   
   public void setSubjectName(){
       if(this.documentTemplateId != null && documentTemplates.get(this.documentTemplateId) == 'Gilead AE Form'){
            subject = this.getGileadSubject(adverseEvent);
       }
       else{
           subject = adverseEvent.Document_File_Name__c;       
       }
   }
    
    public SelectOption[] getTemplates() {
        SelectOption[] templates = new SelectOption[]{ new SelectOption('', '-None-') };
        
        documentTemplates = new Map<Id,String>();
        for (EmailTemplate et : [select Name from EmailTemplate where Folder.Name = 'Adverse Events Templates']) {
            templates.add(new SelectOption(et.Id, et.Name));
            documentTemplates.put(et.Id, et.Name);  
        }
        
        return templates;
    }
    
    
    public PageReference sendEmail() {
        
        //mergeContactsAddresses();
        
        MandrillClient.Message message = new MandrillClient.Message();
        addEmailAddresses(message.to, toAddresses, 'to');
        addEmailAddresses(message.to, ccAddresses, 'cc');
        addEmailAddresses(message.to, bccAddresses, 'bcc');
        
        if (!message.to.isEmpty()) {
            
            //User user = [select Name, Email from User where Id = :UserInfo.getUserId()];                    
            //message.from_name = user.Name;
            //message.from_email = user.Email;
            
            message.from_name = GLOBAL_EMAIL_NAME;
            message.from_email = GLOBAL_EMAIL;
            
            this.setSubjectName();
            
            message.html = body;
            message.subject = subject;
            message.campaign = 'GPS'; //TODO: Change for other
            message.what = adverseEvent.Id;
            
            //Attachment
            Blob file = new PageReference('/apex/DocumentMerge?id=' + adverseEvent.Id + '&tid=' + documentTemplateId).getContent();
            MandrillClient.MandrillAttachment attach = new MandrillClient.MandrillAttachment();
            //attach.name = documentTemplates.get(documentTemplateId) + '.pdf';
            attach.name = subject + '.pdf';
            attach.type = 'application/pdf';
            attach.content = Encodingutil.base64Encode(file);
            message.attachments.add(attach); 
            
            String allRecipients = '';
            
            for (MandrillClient.Recipient recipient : message.to) {
                allRecipients += ', ' + recipient.email;
            }
            
            allRecipients = (allRecipients != '' ? allRecipients.substring(2) : '');
            
            try {
                Map<String,String> response = MandrillClient.send(new MandrillClient.Message[]{ message });
                Task[] tasks = MandrillClient.insertTasks();
                
                for (Task t : tasks) {
                    t.Adverse_Events_Recipients__c = allRecipients;
                }
                update tasks;
                
                Attachment a = new Attachment();
                a.Name = attach.name;
                a.ContentType = attach.type;
                a.Body = file;
                a.ParentId = adverseEvent.Id;
                insert a;
                
                return new PageReference('/' + adverseEvent.Id);
            }
            catch (Exception e) {
                addErrorMessage(e.getMessage());
            }                            
        }
        
                        
        return null;
    }
    
    
    private void mergeContactsAddresses() {
        String[] cids = new String[]{};
        
        for (String cid : toContacts.split(';')) {
            cids.add(cid);
        }
        for (String cid : ccContacts.split(';')) {
            cids.add(cid);
        }
        for (String cid : bccContacts.split(';')) {
            cids.add(cid);
        }
        
        Map<String,String> contactEmails = new Map<String,String>();
                
        contactNames = new Map<String,String>();
        for (Contact c : [select Name, Email from Contact where Id = :cids and Email <> null]) {
            contactNames.put(c.Email.toLowerCase(), c.Name);
            contactEmails.put(c.Id, c.Email.toLowerCase());
        }
        
        for (String cid : toContacts.split(',')) {
            if (contactEmails.containsKey(cid)) {
                if (toAddresses != null && toAddresses.trim() != '') {
                    toAddresses += ',';
                }
                toAddresses += contactEmails.get(cid) + ',';
            }
        }
        for (String cid : ccContacts.split(';')) {
            if (contactEmails.containsKey(cid)) {
                if (ccAddresses != null && ccAddresses.trim() != '') {
                    ccAddresses += ',';
                }
                ccAddresses += contactEmails.get(cid) + ',';
            }
        }
        for (String cid : bccContacts.split(';')) {
            if (contactEmails.containsKey(cid)) {
                if (bccAddresses != null && bccAddresses.trim() != '') {
                    bccAddresses += ',';
                }
                bccAddresses += contactEmails.get(cid) + ',';
            }
        }        
    }
    
    
    private void addEmailAddresses(MandrillClient.Recipient[] recipients, String addr, String type) {
        if (addr != null) {
            for (String email : addr.split(',')) {
                if (email != null && email.trim() != '') {
                    MandrillClient.Recipient recipient = new MandrillClient.Recipient();
                    recipient.email = email.trim().toLowerCase();
                    
                    //if (contactNames.containsKey(recipient.email)) {
                    //   recipient.name = contactNames.get(recipient.email);    
                    //}
                    
                    recipient.type = type;
                    recipients.add(recipient);              
                }
            }
        }
    }
    
    
    public PageReference cancel() {
        if (adverseEvent != null) {
            return new PageReference('/' + adverseEvent.Id);
        }
        else {
            return new PageReference('/home/home.jsp');
        }
    }
}
 
I have a problem with an integration job and a trigger that seem to be colliding and I was wondering if anyone could help me. There is an integration job running in PowerCenter that takes opportunities and creates contacts from the information on the opportunity. There is also a trigger that runs to update the contact roles when these contacts are created. The trigger is causing problems with the power center job and the power center log shows this error:

2015-06-16 02:58:33 : ERROR : (28559 | WRITER_1_*_1) : (IS | Int_Serv_pcpw_Unicode) : node01_pcpw : WRT_8164 : Error loading into target [Contact] : Error received from salesforce.com. Fields []. Status code [CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY]. Message [ContactOpportunityAssoc: execution of AfterInsert caused by: System.DmlException: Insert failed. First exception on row 200; first error: REQUIRED_FIELD_MISSING, Required fields are missing: [ContactId]: [ContactId]

This is causing some contacts not to get created.

The trigger is called ContactOpportunityAssoc and here's the code:
trigger ContactOpportunityAssoc on Contact (after insert) {
set<String> custnmbr = new set<String>();
Map<String,Id> mapCnts = new Map<String,Id>();
for (Contact c: Trigger.new){
if (c.cnt_plcsc_CustNmbr_ExtID__c != null)
{
custnmbr.add(c.cnt_plcsc_Customer_Number__c);
}
/*List all Contacts that have the customer number the same as the loaded custnmbr set*/ 
mapCnts.put(c.cnt_plcsc_Customer_Number__c , c.Id);
}

List<OpportunityContactRole> ocr = new List<OpportunityContactRole>();
/*List all Contacts that have the customer number the same as the loaded custnmbr set*/
for(Opportunity opp: [Select Id, Customer_Number__c 
From Opportunity 
Where Customer_Number__c IN: custnmbr]){

OpportunityContactRole ocr2 = new OpportunityContactRole();
ocr2.ContactId = mapCnts.get(opp.Customer_Number__c);
ocr2.OpportunityId = opp.Id;
ocr2.IsPrimary = true;
ocr2.Role = 'Primary Insured';
/*System.debug('DLB932 - '+ocr2);*/
ocr.add(ocr2);
}

/*Insert the new list of OCR's*/
if(!ocr.isEmpty())
{insert ocr;}

Thank you!
I'm on the last step of the Lightning App Builder trail on the intermediate Admin Trail.  I've download the custom components from the app exchange and confirmed that they are listed in my dev org.  However, when I go to my Lightning app I built, there are no custom components available.  What am I missing?  Thank you for your help.
I have a Process Builder that runs when a task is created. The idea is that it will update the WhoID. It checks the WhatID to see if it starts with a particular 3 characters that relate to a custom object.  If it does then it triggers a flow.  It sends the TaskID and the custom object ID to the flow.  The flow uses the custom object ID to find the related contact ID.  It sends me an email with the TaskID, Contact ID and Custom Object ID for troubleshooting.  Then it does a record update to update the WhoID field on the task with the Contact ID.  This works fine when the task record is created by another process.  If I create the task manually, the process fires, the flow sends me an email with all the correct IDs but the WhoID is not updated.  I added a text field on the task object and tried updaing that with the ContactID and it works in every scenario.  I have also made the launch of the flow a scheduled action so that it fires an hour after the task has been created but that does not work either.  
I have posted this in the success community and got some help there but so far the issue is not solved.  Thank you in advance for your assistance.
I've built the variable Account Revenue according to the instructions but when I verify the step I get this error "Variable 'AccountRevenue' isn't properly configured. Check the instructions."  Here's a screen shot of what my variable looks like.  Please can you help me find my mistake?  Thanks!
User-added image
I'm trying to write test code for a class that is invoked by a custom button.  This is the first time I've done this and lots of googling has not provided me with the answer!

Thank you very much for your help on this!

This is the button:
/apex/SendAdverseEvents?id={!Adverse_Event__c.Id}

I have successfully created an Adverse Event in my test code but and I think I need to instantiate the SendAdverseEventsController but I'm not sure how to do this.

Here is my test code so far:
@isTest
public class SendAdverseEventsTest {
  //Setup test data
  static testMethod void SendAdverseEvtTest() {
        
    Id rtPA = Schema.SObjectType.Account.getRecordTypeInfosByName().get('Person Account').getRecordTypeId();
  
    //create new account for testing       
    Account a = New Account ();
           a.FirstName = 'Robert';
           a.LastName = 'Smith';
           a.RecordTypeId = rtPA;
           a.Email__c = 'test@example.com';
           a.Gender__c = 'Male';
           Insert a;
    

   //find contact ID of the new account
     account RS = [Select PersonContactID from Account where LastName = 'Smith'];
    
    //create adverse event
    Adverse_Event__c AE = New Adverse_Event__c();
        AE.Patient_Identifier__c = RS.PersonContactId;
        AE.Does_the_patient_give_permission_to_call__c = 'Yes';
            AE.Does_the_patient_give_permission_to_cont__c = 'Yes';
          AE.Suspect_Drug_1__c = 'Suspect Drug 1';
          AE.Brand__c = 'Harvoni';
          AE.Is_Patient_Affiliated_with_an_Ambassador__c = 'No';
          Insert AE;
        system.debug(AE);
       
        //find ID of the new adverse event
       Adverse_Event__c AECreated = [Select ID, Name, Auto_Initials__c, Suspect_Drug_1__c, Document_File_Name__c, Document_File_Name_Gilead__c, CreatedDate from Adverse_Event__c where Patient_Identifier__r.LastName = 'Smith'];
    system.debug(AECreated);
        system.debug(AECreated.id);
    
    
    //test.startTest();
      //SendAdverseEventsController con = new SendAdverseEventsController(); // Create instance of controller class
      //con.adverseEvent.id = AECreated.Id; // Set the Id
      //con.SendAdverseEventsController(); // Call the class method, before calling method, you can populate the fields, which are require in this method
    //test.stopTest();
  }
}
    
The commented code at the end I copied from developer forums but I can't work out how to get it to work.

The SendAdverseEventsController is this:

public with sharing class SendAdverseEventsController extends BaseClass {
    
    public Adverse_Event__c adverseEvent { get; set; }
    public String toAddresses { get; set; }
    public String toContacts { get; set; }
    public String ccAddresses { get; set; }
    public String ccContacts { get; set; }
    public String bccAddresses { get; set; }
    public String bccContacts { get; set; }
    public String documentTemplateId { get; set; }
    public String subject { get; set; }
    public String body { get; set; } 
    public User contactLookup {get; set;}
    public Boolean hasAccess { get; set; }
    
    public static final String GLOBAL_EMAIL_NAME = 'AE Reports';
    public static final String GLOBAL_EMAIL = 'aereports@snow-companies.com';
    
    public String defaultBccEmail {get;set;}
    
    private Map<Id,String> documentTemplates;
    private Map<String,String> contactNames;
    
    
    public SendAdverseEventsController() {
        
        this.defaultBccEmail = GLOBAL_EMAIL;
        this.bccAddresses = GLOBAL_EMAIL;
        
        contactLookup = new User();

        Map<String,String> params = ApexPages.currentPage().getParameters();

        for (Adverse_Event__c ae : [select Name, Auto_Initials__c, Suspect_Drug_1__c, Document_File_Name__c, Document_File_Name_Gilead__c, CreatedDate from Adverse_Event__c where Id = :params.get('id')]) {
            adverseEvent = ae;  
        }
        
        for (EmailTemplate et : [select Subject, HtmlValue from EmailTemplate 
                                  where Folder.Name = 'Adverse Events Emails' 
                                    and Name = 'Email Template']) {
            //subject = et.Subject;
            subject = adverseEvent.Document_File_Name__c;
            body = et.HtmlValue;
        }

        String username = UserInfo.getUserName().split('@')[0];

        if (ApexPages.currentPage().getParameters().get('signature') != null) {
            username = ApexPages.currentPage().getParameters().get('signature');
        }

        for (EmailTemplate et : [select Subject, HtmlValue from EmailTemplate 
                                  where Folder.Name = 'Signatures' 
                                    and Name = :username]) {
            
            if (body == null) {
                body = '';
            }
            
            body += et.HtmlValue;
        }
        List<Adverse_Events_Access__c> access = Adverse_Events_Access__c.getAll().values();
        hasAccess = false;
        for(Adverse_Events_Access__c aea : access){
            if(aea.Username__c == UserInfo.getUserName()){
                hasAccess = true;
                break;
            }
        }
    }
    
    public String getGileadSubject(Adverse_Event__c  ae){
       String dateString = ae.CreatedDate.format('ddMMMyyyy').toUpperCase();
       String subjectFormatted = dateString + '-' + ae.Auto_Initials__c + '-' + ae.Suspect_Drug_1__c + '-' + ae.Name;
       
       return subjectFormatted;
   }
   
   public void setSubjectName(){
       if(this.documentTemplateId != null && documentTemplates.get(this.documentTemplateId) == 'Gilead AE Form'){
            subject = this.getGileadSubject(adverseEvent);
       }
       else{
           subject = adverseEvent.Document_File_Name__c;       
       }
   }
    
    public SelectOption[] getTemplates() {
        SelectOption[] templates = new SelectOption[]{ new SelectOption('', '-None-') };
        
        documentTemplates = new Map<Id,String>();
        for (EmailTemplate et : [select Name from EmailTemplate where Folder.Name = 'Adverse Events Templates']) {
            templates.add(new SelectOption(et.Id, et.Name));
            documentTemplates.put(et.Id, et.Name);  
        }
        
        return templates;
    }
    
    
    public PageReference sendEmail() {
        
        //mergeContactsAddresses();
        
        MandrillClient.Message message = new MandrillClient.Message();
        addEmailAddresses(message.to, toAddresses, 'to');
        addEmailAddresses(message.to, ccAddresses, 'cc');
        addEmailAddresses(message.to, bccAddresses, 'bcc');
        
        if (!message.to.isEmpty()) {
            
            //User user = [select Name, Email from User where Id = :UserInfo.getUserId()];                    
            //message.from_name = user.Name;
            //message.from_email = user.Email;
            
            message.from_name = GLOBAL_EMAIL_NAME;
            message.from_email = GLOBAL_EMAIL;
            
            this.setSubjectName();
            
            message.html = body;
            message.subject = subject;
            message.campaign = 'GPS'; //TODO: Change for other
            message.what = adverseEvent.Id;
            
            //Attachment
            Blob file = new PageReference('/apex/DocumentMerge?id=' + adverseEvent.Id + '&tid=' + documentTemplateId).getContent();
            MandrillClient.MandrillAttachment attach = new MandrillClient.MandrillAttachment();
            //attach.name = documentTemplates.get(documentTemplateId) + '.pdf';
            attach.name = subject + '.pdf';
            attach.type = 'application/pdf';
            attach.content = Encodingutil.base64Encode(file);
            message.attachments.add(attach); 
            
            String allRecipients = '';
            
            for (MandrillClient.Recipient recipient : message.to) {
                allRecipients += ', ' + recipient.email;
            }
            
            allRecipients = (allRecipients != '' ? allRecipients.substring(2) : '');
            
            try {
                Map<String,String> response = MandrillClient.send(new MandrillClient.Message[]{ message });
                Task[] tasks = MandrillClient.insertTasks();
                
                for (Task t : tasks) {
                    t.Adverse_Events_Recipients__c = allRecipients;
                }
                update tasks;
                
                Attachment a = new Attachment();
                a.Name = attach.name;
                a.ContentType = attach.type;
                a.Body = file;
                a.ParentId = adverseEvent.Id;
                insert a;
                
                return new PageReference('/' + adverseEvent.Id);
            }
            catch (Exception e) {
                addErrorMessage(e.getMessage());
            }                            
        }
        
                        
        return null;
    }
    
    
    private void mergeContactsAddresses() {
        String[] cids = new String[]{};
        
        for (String cid : toContacts.split(';')) {
            cids.add(cid);
        }
        for (String cid : ccContacts.split(';')) {
            cids.add(cid);
        }
        for (String cid : bccContacts.split(';')) {
            cids.add(cid);
        }
        
        Map<String,String> contactEmails = new Map<String,String>();
                
        contactNames = new Map<String,String>();
        for (Contact c : [select Name, Email from Contact where Id = :cids and Email <> null]) {
            contactNames.put(c.Email.toLowerCase(), c.Name);
            contactEmails.put(c.Id, c.Email.toLowerCase());
        }
        
        for (String cid : toContacts.split(',')) {
            if (contactEmails.containsKey(cid)) {
                if (toAddresses != null && toAddresses.trim() != '') {
                    toAddresses += ',';
                }
                toAddresses += contactEmails.get(cid) + ',';
            }
        }
        for (String cid : ccContacts.split(';')) {
            if (contactEmails.containsKey(cid)) {
                if (ccAddresses != null && ccAddresses.trim() != '') {
                    ccAddresses += ',';
                }
                ccAddresses += contactEmails.get(cid) + ',';
            }
        }
        for (String cid : bccContacts.split(';')) {
            if (contactEmails.containsKey(cid)) {
                if (bccAddresses != null && bccAddresses.trim() != '') {
                    bccAddresses += ',';
                }
                bccAddresses += contactEmails.get(cid) + ',';
            }
        }        
    }
    
    
    private void addEmailAddresses(MandrillClient.Recipient[] recipients, String addr, String type) {
        if (addr != null) {
            for (String email : addr.split(',')) {
                if (email != null && email.trim() != '') {
                    MandrillClient.Recipient recipient = new MandrillClient.Recipient();
                    recipient.email = email.trim().toLowerCase();
                    
                    //if (contactNames.containsKey(recipient.email)) {
                    //   recipient.name = contactNames.get(recipient.email);    
                    //}
                    
                    recipient.type = type;
                    recipients.add(recipient);              
                }
            }
        }
    }
    
    
    public PageReference cancel() {
        if (adverseEvent != null) {
            return new PageReference('/' + adverseEvent.Id);
        }
        else {
            return new PageReference('/home/home.jsp');
        }
    }
}
 
I'm on the last step of the Lightning App Builder trail on the intermediate Admin Trail.  I've download the custom components from the app exchange and confirmed that they are listed in my dev org.  However, when I go to my Lightning app I built, there are no custom components available.  What am I missing?  Thank you for your help.
It is not possible to mark the trailhead module "Creating and Customizing Lightning Apps" as "read". There is no challenge required, and it says "This unit doesn't have a challenge. You can still earn points by marking the unit as read. Good job!". So, to complete the module, it must be marked as read. I am logged in, and still unable to mark as "read". What am I missing?
Cheers,
Henk
I have been unable to mark the Trailhead module "Creating and Customizing Lightning Apps" as read. There is no assigned challenge, and it says to complete the module, it must be marked as read. I am signed in, and still unable to mark ad read. Any suggestions?

Thanks,
Eli
I do not see any option on the page, however the challenge section says that 'This unit doesn't have a challenge. You can still earn points by marking the unit as read. Good job!' 
This is for Creating and Customizing Lightning Apps trail.