function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Jewelyn FregoeJewelyn Fregoe 

an anyone help fix my Apex Class for my Trigger that posts to a Chatter group each time an Opp Status changes?

I'm still new to writing triggers/Apex Classes, so I think I may have taken on something a bit too complicated for my first one. Any help is appreciated!

Here's my Apex Trigger. I have it in my sandbox and it does work! Everytime an opportunity's status changes, a post is made in a group chatter.

trigger trgr_CreateChatterGroupAlerts on Opportunity (after Update) {

   List<Opportunity> updatedOpptyList = 
       [
           SELECT
               Id,
               Name,
               Account.Name,
               StageName,
               LastModifiedBy.Name
           FROM
               Opportunity
           WHERE
               Id IN :trigger.newMap.keyset()
       ];
       
   String changedOpptyList = '';
   
   for(Opportunity oppty : updatedOpptyList){
       if(oppty.StageName != trigger.oldMap.get(oppty.Id).StageName){
           changedOpptyList +=
               'Opportunity ' + oppty.Name + ' (' + oppty.Account.Name + ')'+
               ' has its Stage changed from ' + trigger.oldMap.get(oppty.Id).StageName + ' to ' +
               oppty.StageName + '\r\n';
       }
   }
   
   if(changedOpptyList!=''){
       FeedItem feedItem = new FeedItem();
    
       feedItem.ParentId = System.Label.Opportunity_Alerts_Group_Id;
       feedItem.Body = changedOpptyList;
    
       INSERT feedItem;
   }
}



I've never written an Apex Class before, so this part is new to me... Here's what I've done so far and it keeps failing!! I am open to starting over if I need to because I can't figure out how to fix it. I think it's probably going wrong at the boolean, but I really don't know.

@isTest
public class trgr_CreateChatterGroupAlerts_Test{
   static Opportunity tOppty;
   static CollaborationGroup chatterGrp;
   
   public static void createTestData(){
       tOppty = new Opportunity(
           Name = 'Test Oppty',
           Proposal__c = 'DMO003',
           StageName = 'Ballpark',
           CloseDate = System.today() + 10
       );
       
       INSERT tOppty;
       
       chatterGrp = new CollaborationGroup(
           Name = 'Test Group',
           CollaborationType = 'Public'
       );
       
       INSERT chatterGrp;
   }
   
   public static void updateOpptyStage(){
       tOppty.StageName = 'Proposal';
       UPDATE tOppty;
   }
   
   public static Boolean isFeedItemCreated(){
       FeedItem feedItem = 
           [
               SELECT
                   Id,
                   ParentId,
                   Body
               
               FROM
                   FeedItem
               
               WHERE
                   ParentId = :chatterGrp.Id
                   
               ORDER BY CreatedDate DESC
               
               LIMIT 1
           ];    
           
           if(feedItem!=NULL && feedItem.Body.contains(tOppty.Name)){
               return TRUE;
           }
           else{
               return FALSE;
           }
   }
   
   public testMethod static void triggerInAction(){
       test.startTest();
           
           createTestData();
           
           updateOpptyStage();
           
           System.assert(true, isFeedItemCreated());
               
       test.stopTest();
   }
}



This is the error message I get when I run it:

Class: trgr_CreateChatterGroupAlerts3_Test
Method Name: triggerInAction
Pass/Fail: Fail
Error Message: System.QueryException: List has no rows for assignment to SObject
Stack Trace: Class.trgr_CreateChatterGroupAlerts3_Test.isFeedItemCreated: line 30, column 1
Class.trgr_CreateChatterGroupAlerts3_Test.triggerInAction: line 63, column 1



Thanks in advance for any help!!!
Best Answer chosen by Jewelyn Fregoe
GauravGargGauravGarg

Hi Jewelyn,

Your code looks good, only thing which is concerned here is ChatterGroupName. 
In Opportunity code, when StageName got updated. The ParentId of FeedItem is "Opportunity_Alerts_Group_Id".
While in test you are trying query FeedItems whose ParentId is in ChatterCollaborationGroup "Name = 'Test Group'". 

Here, it may be a problem. As Opportunity_Alerts_Group_Id may have different ID (Salesforce standard ID), while creating a new chatterCollaborationGroup we will differnt ID (salesforce standard ID). 
Hence there are no matching record and the error occurs. 

Please try below test class code for FeedItem:
public static Boolean isFeedItemCreated(){
       FeedItem feedItem = 
           [
               SELECT
                   Id,
                   ParentId,
                   Body
               
               FROM
                   FeedItem
               
               WHERE
                   ParentId = :System.Label.Opportunity_Alerts_Group_Id;
                   
               ORDER BY CreatedDate DESC
               
               LIMIT 1
           ];    
           
           if(feedItem!=NULL && feedItem.Body.contains(tOppty.Name)){
               return TRUE;
           }
           else{
               return FALSE;
           }
   }

Hope this will work for you. 

Thanks,

Gaurav
Email: gauravgarg.nmims@gmail.com

All Answers

GauravGargGauravGarg

Hi Jewelyn,

Your code looks good, only thing which is concerned here is ChatterGroupName. 
In Opportunity code, when StageName got updated. The ParentId of FeedItem is "Opportunity_Alerts_Group_Id".
While in test you are trying query FeedItems whose ParentId is in ChatterCollaborationGroup "Name = 'Test Group'". 

Here, it may be a problem. As Opportunity_Alerts_Group_Id may have different ID (Salesforce standard ID), while creating a new chatterCollaborationGroup we will differnt ID (salesforce standard ID). 
Hence there are no matching record and the error occurs. 

Please try below test class code for FeedItem:
public static Boolean isFeedItemCreated(){
       FeedItem feedItem = 
           [
               SELECT
                   Id,
                   ParentId,
                   Body
               
               FROM
                   FeedItem
               
               WHERE
                   ParentId = :System.Label.Opportunity_Alerts_Group_Id;
                   
               ORDER BY CreatedDate DESC
               
               LIMIT 1
           ];    
           
           if(feedItem!=NULL && feedItem.Body.contains(tOppty.Name)){
               return TRUE;
           }
           else{
               return FALSE;
           }
   }

Hope this will work for you. 

Thanks,

Gaurav
Email: gauravgarg.nmims@gmail.com

This was selected as the best answer
Jewelyn FregoeJewelyn Fregoe
It worked perfectly!!! I’ve spent hours trying to fix this… THANK YOU!!!!
James LoghryJames Loghry
So  I just saw Gurav responded, but I wanted to add a few things.  First is, I think you're closer than you might think.

You're storing the Chatter Group Id in a Custom Label.  However, I would change it to either a custom setting or custom metadata. With a custom setting, you can actually insert values in your test class, which you will need as Gaurav alluded to.

Once you update the label to a custom setting, you can refactor your trigger like so:
 
trigger trgr_CreateChatterGroupAlerts on Opportunity (after Update) {

    //Assumes your custom setting is called MyCustoMSetting, and has a custom field called Value, and is populated with a Name of OpportunityChatterGroup.
    MyCustomSetting__c setting = MyCustomSetting__c.getValues('OpportunityChatterGroup');
    Id chatterGroupId = setting.Value__c;

   List<Opportunity> updatedOpptyList = 
       [
           SELECT
               Id,
               Name,
               Account.Name,
               StageName,
               LastModifiedBy.Name
           FROM
               Opportunity
           WHERE
               Id IN :trigger.newMap.keyset()
       ];
       
   String changedOpptyList = '';
   
   for(Opportunity oppty : updatedOpptyList){
       if(oppty.StageName != trigger.oldMap.get(oppty.Id).StageName){
           changedOpptyList +=
               'Opportunity ' + oppty.Name + ' (' + oppty.Account.Name + ')'+
               ' has its Stage changed from ' + trigger.oldMap.get(oppty.Id).StageName + ' to ' +
               oppty.StageName + '\r\n';
       }
   }
   
   if(changedOpptyList!=''){
       FeedItem feedItem = new FeedItem();
    
       feedItem.ParentId = chatterGroupId;
       feedItem.Body = changedOpptyList;
    
       INSERT feedItem;
   }
}

Next, you would update your unit test to create the custom setting and add the chatter group that you create in your data setup method.
 
@isTest
public class trgr_CreateChatterGroupAlerts_Test{
   static Opportunity tOppty;
   static CollaborationGroup chatterGrp;
   
   public static void createTestData(){
       tOppty = new Opportunity(
           Name = 'Test Oppty',
           Proposal__c = 'DMO003',
           StageName = 'Ballpark',
           CloseDate = System.today() + 10
       );
       
       INSERT tOppty;
       
      
       chatterGrp = new CollaborationGroup(
           Name = 'Test Group',
           CollaborationType = 'Public'
       );
       
       INSERT chatterGrp;

       MyCustomSetting__c setting = new MyCustomSetting__c(Name='OpportunityChatterGroup',Value__c=chatterGrp.Id);
      insert setting;
   }
   
   public static void updateOpptyStage(){
       tOppty.StageName = 'Proposal';
       UPDATE tOppty;
   }
   
   public static Boolean isFeedItemCreated(){
       FeedItem feedItem = 
           [
               SELECT
                   Id,
                   ParentId,
                   Body
               
               FROM
                   FeedItem
               
               WHERE
                   ParentId = :chatterGrp.Id
                   
               ORDER BY CreatedDate DESC
               
               LIMIT 1
           ];    
           
           if(feedItem!=NULL && feedItem.Body.contains(tOppty.Name)){
               return TRUE;
           }
           else{
               return FALSE;
           }
   }
   
   public testMethod static void triggerInAction(){
       test.startTest();
           
           createTestData();
           
           updateOpptyStage();
          
               
       test.stopTest();

       System.assert(true, isFeedItemCreated());
   }
}

FYI, I also moved System.assert below Test.stopTest, just to make sure that all transactions have been completed prior to asserting the chatter group is created.

Also, to play admin / devil's advocate here, any particular reason you aren't looking at a Process to do this? A process can run on record update and has the ability to do Chatter posts too, even to a Group.  If you're writing this trigger as a development excercise, that's fine, but this is a easy peasy Process to implement.
James LoghryJames Loghry
One more thing to add, if / when you post code on the forums, please use the code format button (< >). It helps with the readability a bit, and also allows responders to copy / paste if need be.  Thanks!
GauravGargGauravGarg
Hi Jewelyn,

Thanks. Happy to help you. 

Thanks,
Gaurav 
Email: gauravgarg.nmims@gmail.com
Jewelyn FregoeJewelyn Fregoe
Thanks for the feedback, James! This is more of a learning exercise for me than anything else.