Thursday, January 24, 2013

Adding an Activity to a Chatter Feed Using a Trigger

I have had many requests for a trigger like this over time and Kevin Swiggum had a post back in 2010 with a simple to implement solution for this request, the link is below.

http://www.radialweb.com/2010/09/adding-activity-to-a-chatter-feed-using-triggers/


Below is a copy of the code Kevin posted:

trigger ChatterActivity on Task (after insert, after update) {

    List<FeedItem> feedItems = new List<FeedItem>();

    //We want to show the User name as assignedTo. The only way to get to that is by querying the user table.
    Set<ID> ownerIds = new Set<ID>();
    for (Task t : Trigger.new) {
        ownerIds.add(t.ownerId);
    }
    Map<ID,User> userMap = new Map<ID,User>([SELECT ID, Name FROM User WHERE ID IN :ownerIds]); //This is our user map

    //Now loop though the new/updated tasks and create the feed posts
    for (Task t : Trigger.new) {
        if (t.WhatId != null) {
            FeedItem fitem = new FeedItem();
            fitem.type = 'LinkPost';
            fitem.ParentId = t.WhatId;
            fitem.LinkUrl = '/' + t.id; //This is the url to take the user to the activity
            fitem.Title = 'View';  //This is the title that displays for the LinkUrl

            //Get the user by checking the userMap we created earlier
            User assignedTo = userMap.get(t.ownerId);

            fitem.Body = ((Trigger.isInsert) ? 'New' : 'Updated') + ' Activity ' + ((t.ActivityDate != null) ? t.ActivityDate.format() :'')
                        + '\nAssigned To: ' + ((assignedTo != null) ? assignedTo.name : 'Unknown')
                        + '\nSubject: ' + t.Subject
                        + '\nStatus: ' + t.Status;

            feedItems.add(fitem);
        }
    }

    //Save the FeedItems all at once.
    if (feedItems.size() > 0) {
        Database.insert(feedItems,false); //notice the false value. This will allow some to fail if Chatter isn't available on that object
    }
}


And here is the test class associated

public with sharing class ChatterUnitTests {

    /*
    *    Test the ChatterActivity trigger which inserts chatter feed posts whenever a task is inserted on the parent object
    */
    public static testMethod void testChatterActivity() {
        //create the test account
        Account a = new Account(name='Test Account');
        insert a;

        //create a task on that account
        Task t = new Task(whatId=a.id);
        t.Subject = 'This is a test activity for chatter';
        t.ActivityDate = System.today();
        t.Status = 'In Progress';
        t.Description = 'Hello, this will be chattered';

        insert t;

        //Make sure Account has the feed enabled. If it does, make sure the chatter feed post is there
        Schema.DescribeSObjectResult r = Account.SObjectType.getDescribe();
        if (r.isFeedEnabled()) {
            List<AccountFeed> posts = [SELECT Id, Type FROM AccountFeed WHERE ParentId = :a.id];
            System.assertEquals(1, posts.size());
        }
    }
}







Wednesday, January 16, 2013

Bulkify Apex Triggers

This is something that has been harped on in every Apex class I have taken and it finally clicked with me! This is probably much more clearly explained in the link below from developer.force.com but I want to document it here for my own knowledge.

http://wiki.developerforce.com/page/Best_Practice:_Bulkify_Your_Code

  • Create a SET before entering trigger.new that will hold the ID of all SObjects being updated or looped through
  • Pull a list of the IDs of the SObjects that need to be updated when looping through trigger.new and add them to the SET defined above
  • If that SET contains items then create a list of SObjects and use 1 SOQL query to populate the entire list
  • Now you can loop through the entire list of SObjects and make updates from there...

Below is an example of this in action:

if(trigger.isBefore){
//Set to hold the list of Contract Id's that will be queried for the associated Account 
Set<String> ContractIds = new Set<String>();
//loop through all Agency Commission Records and add the Id of the Contract to the ContractIds Set
for(Agency_Commission__c ac : trigger.new){
if(ac.Contract__c != null){
ContractIds.add(ac.Contract__c);
}
}
if(!ContractIds.isEmpty()){
//Use a single query to pull a list of Contract SObjects
List<Asset_C__c> contractList = [select id, Account__c from Asset_C__c where id =: ContractIds];

//Map to hold the Contract ID as the Primary Key and the Account ID as the Key Value
Map<string, string> ContractAndAccount = new Map<string, string>();
//Loop through the List of Contracts and add the Contract ID and Account ID to the map
for(Asset_C__c c1 : contractList){
if(c1.Account__c != null){
ContractAndAccount.put(c1.id, c1.Account__c);
}
}
if(!ContractAndAccount.isEmpty()){
//Loop through the original Agency commission values and assign the Account based on the ContractAndAccount Map
for(Agency_Commission__c acUpdate : trigger.new){
//retrieve the Account from the ContractAndAccount Map and assign it to a string value
String acctForContract = ContractAndAccount.get(acUpdate.Contract__c);
acUpdate.Client__c = acctForContract;
}
}
}
}

Friday, December 21, 2012

Delete Triggers and Classes from Production in Salesforce using Eclipse IDE

In order to remove a class or trigger from production using the force.com ide you can do the following:

1. Pull up the XML file associated to the class/trigger, it will look something like this:

<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>21.0</apiVersion>
    <packageVersions>
        <majorNumber>1</majorNumber>
        <minorNumber>3</minorNumber>
        <namespace>households</namespace>
    </packageVersions>
    <status>Active</status>
</ApexClass>
2. Where is says <status> replace "Active" with either "Inactive" or "Deleted" then save the XML.
3. Now deploy this class/trigger to production and it will be removed!

Friday, November 2, 2012

Goldmine Query for Primary Email


SELECT contact1.ACCOUNTNO AS CustomerID, contact1.contact AS Contact, COALESCE(em.contsupref, '')+COALESCE(em.address1, '') AS Email, em.mergecodes
FROM contact1
 JOIN contsupp em on contact1.accountno=em.accountno
WHERE em.contact='E-Mail Address'
 AND em.rectype LIKE 'P'
 AND em.zip LIKE '_1%'

Monday, October 22, 2012

Goldmine - Exporting History and Email Bodies in One Step

STEP 1
First create a view in SQL that will do a left join of the ContHist and Mailbox tables. This means that it will pull in every single ContHist record and only pull in information from the Mailbox table where there is a match on the field they are joined on.

NOTE: I am only including the columns that I typically use in a migration, there are more ContHist and Mailbox columns to be aware of. Also I am only pulling the first 4,000 characters of both the notes and mail messages because of the limitations of Excel. As part of this view we are converting both Notes (on ContHist) and RFC822 (on Mailbox) from blob to varchar(4000).

SQL QUERY 1


GO
CREATE VIEW [dbo].[HistoryANDMail]
AS

Select

c.USERID, c.ACCOUNTNO, c.RECTYPE, c.ONDATE, c.REF as [Subject History - REF],
CAST(CAST(c.NOTES AS varbinary(4000)) AS varchar(4000)) AS NOTES,
c.LINKRECID, c.recid,

CAST(CAST(m.RFC822 AS varbinary(4000)) AS varchar(4000)) AS RFC822

From Conthist as c
Left Join MAILBOX as m
ON c.LINKRECID = m.recid
WHERE c.ACCOUNTNO is not null and datalength(c.ACCOUNTNO)>0

END SQL QUERY 1


STEP 2
Now you have a view that contains all ContHist data converted and ready to be exported to excel. I like to try to strip the HTML before going into excel because they excel files can be very large and hard to deal with. Here is the process for this. Following a great blog post you can create a function that removes HTML from specific columns.

Essentially copy and paste this into a query and run it, this will create a user defined function we will use later.

SQL QUERY 2


CREATE FUNCTION [dbo].[udf_StripHTML]
(@HTMLText VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE @Start INT
DECLARE @End INT
DECLARE @Length INT
SET @Start = CHARINDEX('<',@HTMLText)
SET @End = CHARINDEX('>',@HTMLText,CHARINDEX('<',@HTMLText))
SET @Length = (@End - @Start) + 1
WHILE @Start > 0
AND @End > 0
AND @Length > 0
BEGIN
SET @HTMLText = STUFF(@HTMLText,@Start,@Length,'')
SET @Start = CHARINDEX('<',@HTMLText)
SET @End = CHARINDEX('>',@HTMLText,CHARINDEX('<',@HTMLText))
SET @Length = (@End - @Start) + 1
END
RETURN LTRIM(RTRIM(@HTMLText))
END
GO

END SQL QUERY 2

STEP 3
Now we need to run an export task sending a query we will write into an excel file. Right click on the database > tasks > export data... When it comes time for the query enter the following.

NOTE: this has to be done in sets of 50,000 due to the size limitation of a spreadsheet in excel 2007 and I was unable to run this on a spreadsheet using 2010.



SQL QUERY 3


SELECT

USERID, ACCOUNTNO, RECTYPE, ONDATE, [Subject History - REF], 
dbo.udf_stripHTML(NOTES) as NOTES, LINKRECID, recid, dbo.udf_stripHTML(RFC822) as RFC822

from 

( SELECT
    ROW_NUMBER() OVER (ORDER BY recid ASC) AS ROW_NUMBER,
    *
  FROM dbo.FINALHistoryANDMail
) foo
WHERE ROW_NUMBER <= 50000


END SQL QUERY 3


Hope this helps! 

Strip HTML from SQL Columns

Just came across this, great info!

http://blog.sqlauthority.com/2007/06/16/sql-server-udf-user-defined-function-to-strip-html-parse-html-no-regular-expression/


Goldmin Use:
What I did was create a view converting the image fields in SQL to varchar, then when exporting the query I ran the udf_stripHTML(column) function so when the data is being exported to excel the HTML is coming off then.

SQL Row Count for All Tables

There have been times I want to see how many rows are in each table of a database without going into each table and running a query to see the row count. This returns a list of all tables with more than 2 rows of data and the row count for each table.

SELECT OBJECT_NAME(OBJECT_IDTableNamest.row_count
FROM sys.dm_db_partition_stats st
WHERE index_id 2
ORDER BY st.row_count DESC