This post describes an issue and the solution to an error received when trying to push a sandbox between Salesforce environments that are of different releases/versions (eg Spring13 vs. Summer13). The solution was provided by Salesforce support.

Problem: If a sandbox has been upgraded to a new Salesforce release and the Production environment has not. When attempting to upload the change sets to production (or another sandbox) you may see this error message:

“This change set requires the “28.0” or later platform version. One or more of the listed organizations is on a version incompatible with this change set. You can only select an organization for upload that is running the required version or later.”

Reason this occurs: The target org is selected the first time a user uploads a change set. Based on the target org, Salesforce sets the version of the change set (current release vs next release) to match the target org’s version. Once this is done, Salesforce requires that subsequent uploads of that change set be targeted to other orgs on the same version, or a later (newer) version, used in the original upload.

Scenario 1 :

Change Set “A” is created in a Winter ’13 org
• The first upload attempt of Change Set “A” targets a Winter ’13 org and is successful
• A second upload attempt of Change Set “A” targets a Spring ’13 org and is successful
• Both attempts were successful since the first upload attempt locked the change set to Winter ’13. Subsequent upload attempts of the original change set must be targeted to other Winter ’13 or Spring ’13 orgs based on the “Reason this occurs:” wording above.

Scenario 2:

• Change Set “A” is created in a Winter ’13 org
• The first upload attempt of Change Set “A” targets a Spring ’13 org and is successful
• A second upload attempt of Change Set “A” targets a Winter ’13 org and hits the error
• It’s expected that the second upload failed since the first upload targeted a Spring ’13 org. Subsequent upload attempts of the original change set must be targeted to other Spring ’13 orgs based on the “Reason this occurs:” wording above.

Workaround for Scenario 2:

• Clone Change Set “A”
• Upload of the cloned change set to a Winter ’13 org (Scenarion 2 above) is successful
• Why does the act of cloning the change set resolve the issue? A cloned change set is treated as a new change set. Since it has yet to be uploaded to an org, it’s version hasn’t been locked (as described in “Reason this occurs:” above). Therefore, it can be uploaded to a current version or a Preview sandbox.

Solution: In short; if you find your self moving change sets between different environments after a sandbox upgrade you may need to upload te change set to a sandbox that hasn’t yet been upgraded or to production to lock in the API version of the change set.

UPDATE Fri 31 May: I am excited to share that I have turned this project into an AppExchange app.  It will be released under a new company called Tapply.  The product will be called Chatter Mention More and will support mentioning groups and creating custom tags to reference records. Find out more here.

 

 

Chatter Mention for Groups is an often requested feature.  The idea has 6500 points and heaps of comments.  This video shows how it could work.

Please provide any feedback or questions in the comments below or reach out to me on twitter @andymahood__c

So… how does this work?

For those who are interested in how this works let me explain…  …for those who are not go here.

When a Chatter Group is created there is a trigger to create a new user.  Now dont worry too much we are not using a Standard or Platform licence but one of the 5000 chatter free licences that are given away with every instance of Salesforce.  This user acts as a broker to the Group.  If the new Group we created was called ‘Marketing Analysis’ then the user will be called ‘Marketing Analysis (Group)’.  This is how we get the standard mention functionality to work.  Marke sense so far?

Now when a new post is created we check if the mentioned user is associated to a group, if so we crank out some magic and duplicate the post onto the group feed.  When the post is against an object we add a link to the record (this my need to change as you cant have an image post and a link, or if the original post was a link then we can have both the original link and a the link to reference the record it was posted against).

As we duplicate the post we write a mapping record into a table so if someone comments against either the new post or the original post then we know we should duplicate the comment over to the other post.

Nice, perfect solution eh?  Well there are some issues.

Duplication in the feed: Due to the process of duplicating the post if you are a subscriber of the Group as well as someone who follows the Record or the User then you will see a duplication in your Chatter feed.

We use a licence for every group: Even though the licences we use are free it does eat into your allocation.  Nothing in life is free they are bundled with other licences.  The silver lining is that Salesforce have been known to drop a few more thousand Chatter free licences in an org if you ask nicely and require them.  Also this is why you get the message about the user not being able to view the record – they are Chatter free users so cannot see anything.  On the plus side the warning serves nicely to the fact that there may be people in the Group that cannot see the record.

When you delete a group the user remains, FOREVER!: This is another side (a)(e)ffect of using a User as the broker.  If you decide to delete the group you cannot delete the user so you will always have a certain number of Users sitting around doing nothing just because you used to have a ‘January Gym Club’ chatter group.  Its on the backlog to de-activate the user on deletion to reclaim the licence so that will help.

and thats all I can think of now.

Over the weekend Aldo Fernandez (@aldoforce) asked the question “is there a way to get from apex code a PNG/JPG snapshot of a Salesforce Dashboard”.  Aldo was looking to use the dashboard chart in a dynamic visualforce page.

This week I have been playing with a very similar requirement of displaying a report or dashboard chart on a standard page layout.  My solution involves aligning an iframe within a DIV to display only the chart component of the report.  Aldo identified the UX issues of using an iframe “the ‘right click’ issue and any click will launch a request from within the iframe. Bad UX”.  At this point Daniel Hoechst (@dhoechst) shared that it is possible to “Grab the url of the dashboard component image to use. When you refresh dashboard, image gets updated.”.

This has lead me to identify the two known methods for showing a report or dashboard chart on a visualforce page.  Both of these methods are non-supported by Salesforce so are vulnerable to changes of the UI.

1. iFrame the report result page

Method one is to use a Visualforce component to help embed a standard report chart using an iFrame.  The iFrame is loaded within a DIV window.  The iFrame element is then positioned within the DIV window so only the chart can be seen.

Advantages: Criteria parameters can be passed to the component to make the chart output dynamic for the current page, e.g. Show all Opportunities or Cases for current Account.

Disadvantages: Requires an iFrame. Any changes to source chart will impact the layout.  As Aldo mentioned there are UX issues surrounding the use of an iFrame.

Find the component and example code on Bitbucket: http://bitbucket.org/tapply_andy/dynamic-report-chart-component

Chart in Standard Page Layout

2. Dashboard Image URL

Method two is from Daniel Hoechst (@dhoechst).  This method involves creating a dashboard chart and then referencing the image.  This method allows the developer to control the eventual output of the dashboard chart with the following table of URL parameters.  I haven’t figured out what every parameter does yet so if you know or can figure it out please leave a comment and I can update this table.

Advantages: Actual image in embedded into the page which will give the developer more control of where and how the chart image can be used.  The additional URL parameters provide further control to the developer.

Disadvantages: The chart component needs tone created on a dashboard.  Doesn’t offer the same flexibility in terms of report criteria as method one.

URL Param Purpose
rsid=0FL200000009AOw Dashboard Chart ID
ruid=005200000017TsH Run As User ID
did=01Z20000000XlMf Dashboard ID
s=7 Size of Chart 1,2,3,4,5,6,7
fs=10 Aixis FontSize
tfg=12 Title FintSize
tfs=-16777216
explode=0
c=bar chart type (e.g. bar, line, etc)
cs=0 chart sub type (stacked, split, stacked to 100%) 0,1,2,3
title=  chart title
eh=no
compo=yes
fg=-16777216 axis font colour
bg1=-1 colour of bg from left side of chart
bg2=-1 colour of bg from right side of chart
bgdir=2  direction of fade from bg1 to bg2
dl1=Case+Owner Axis Label
dl2=
l=2
sax=yes show axis labels (default no)
Yman=no
nc=0 rows to filter, integer is how many 0 is all
actUrl=
sd=1 sort direction 0,1,2,3,4
scv=no show values
sct=no
spt=no
cu=GBP
ab=X
u=0
vt=0
ab2=Y
u2=0
vt2=0
vl0=Record+Count Axis Label
spoc=no
topn=no
gc0=-1
gc1=-1
gc2=-1
refreshts=1352192506000

UPDATE Fri 31 May: I am excited to share that I have turned this project into an AppExchange app.  It will be released under a new company called Tapply.  The product will be called Autocomplete Lookups for Salesforce and will allow you to search using any field on the related object and display any field as the label in the search results. Find out more here.

 

A few weeks ago I demoed Autocomplete for Salesforce and showed how it can make lookup fields in Salesforce faster and easier to fill in.  This video shows additional functionality for searching based on fields other then ‘Name’.

UPDATE Fri 31 May: I am excited to share that I have turned this project into an AppExchange app.  It will be released under a new company called Tapply.  The product will be called Chatter Mention More and will support mentioning groups and creating custom tags to reference records. Find out more here.

 

 

A key feature missing from Salesforcr Chatter is the ability to @mention or hashtag records. There is a popular idea for the feature here.

I have created a proof of concept that works for accounts and uses the standard ticker symbol field and a $tag for mentioning Accounts in chatter posts.  When the Account is tagged the post appears on both the Group/Feed it was originally posted and the Account feed.

This is a preview video showing how it works, let me know your feedback and feature suggestions in the comments below, comments on youtube or on twitter @andymahood__c.

(please excuse the low production quality, this is just a proof of concept at this stage)

'Just found out, Salesforce has Autocomplete'

‘Just found out, Salesforce has Autocomplete’

UPDATE Fri 31 May: I am excited to share that I have turned this project into an AppExchange app.  It will be released under a new company called Tapply.  The product will be called Autocomplete Lookups for Salesforce and will allow you to search using any field on the related object and display any field as the label in the search results. Find out more here.

 

This week I cooked up an Autocomplete component for Salesforce.  Once installed it allows any Lookup field to support autocomplete.

How it works

Autocomplete for Salesforce works by installing a homepage component in the left hand column which finds all the lookup fields on the page ‘autocompletifies’ them.  This means that as you start typing Autocomplete for Salesforce starts suggesting records in a similar way to Google.

How to get it

Autocomplete for Salesforce is very new and only available in BETA.  This means it can currently only be installed in Developer and Sandbox environments.  If you are interested and would like to test this in your Developer or Sandbox environment then you can follow these instructions:

Installing the Managed Package

  • While logged into the Developer or Sandbox environment you would like to install Autocomplete for Salesforce into append the following to the URL[domain eg. na1].salesforce.com/packaging/installPackage.apexp?p0=04tG0000000JXvz
  • Salesforce will guide you through the steps to install the Managed Package into your environment
Example of Autocomplete for Salesforce

Example of Autocomplete for Salesforce

Once installed there are a few steps before you can get going:

  • Autocomplete for Salesforce relies on some smarts concealed within a homepage component in the left hand nav of Salesforce.  To enable this navigate to Setup > Customise > Home > Homepage Layouts then edit each layout selected the ‘Autocomplete’ component
  • Navigate to the User Inserface settings in the setup menu and check that the option ‘Show Custom Sidebar Components on All Pages’ is checked
  • Autocomplete for Salesforce allows you to optionally enable Autocomplete for each object.  to do this navigate to Setup > Develop > Custom Settings then select Manage against the Custom Setting called ‘sObjectMap’.  Select the new button, in the Name field enter the three characters at the start of the records ID (e.g. all Account IDs start with 001).  Then in the API Name field enter the Salesforce API name for the object, in this example its Account.
  • Find an Opportunity and select edit and in the Account lookup field start typing the name of an Account, you should see Autocomplete for Salesforce start to suggest some Accounts for you.

Thats it! Any issues with the steps reach out to me on twitter @andymahood__c

Whats next…

Once I am 80% confident that any major issues are identified and under control Autocomplete for Salesforce will be provided on the AppExchange and then very shortly after open sourced on GitHub.

UPDATE @ Wed 14th Nov 19:45GMT: Updated managed package URL with latest version including error handling and printing query execution time for debugging.

UPDATE @ Wed 14th Nov 20:15GMT: Updated managed package URL with latest version including support for inline edit.

UPDATE @ Fri 11th Jan 15:50GMT: Updated managed package URL with latest version including error reporting.

Often its required to embed a Visualforce page into either the Home tab or the left hand navigation.  This is not natively possible, however a common work around is to use a homepage component with an iFrame referencing your Visualforce page.

The next challenge is that the iFrame height is static as defined in the component.  Javascript hack to the rescue!

<apex:page id="hompage" title="Homepage Visualforce"
    controller="" showheader="false" sidebar="false" cache="false" standardStylesheets="false">
<script type="text/javascript">
    function resizeFrame() {
	var parentIFrame = parent.document.getElementById('homepageComponentiFrameId');
            if ( parentIFrame != null ) {
                 if ( document.body.clientHeight != null && document.body.clientHeight > 0 )
                      parentIFrame.height = document.body.clientHeight;
                  else {
                       parentIFrame.height = document.height+"px";
                  }
              }
         }
</script>
     <body onload="resizeFrame();">
your page contents here...
</body>
</apex:page>

This simple example Visualforce page calls a simple Javascript method onload.  The resizeiFrame method could also be called by a chunk of Javascript loaded at the end of the page to save adding the body tags, which may break the markup.

You then need your homepage component which looks like this:

<iframe id="homepageComponentiFrameId" src="/apex/yourVisualforcePage"
width="100%" height="5px" frameborder="0" scrolling="no"> </iframe>

The important part here is to make the id of the iFrame the same as the value in the parent.document.getElementById line in the Javascript in the Visualforce page.

You will find the page load in the frame and then resize, the resize is sometimes noticable if your browser is slower at rendering the page and executing Javascript.

Let me know if this works for you, I have used this many times in an emea org.

Over the next two weeks I will be preparing to get Add Followers for Chatter available for Free from the AppExchange.  The product itself is ready, feel free to install in a Sandbox and test, all that is outstanding is making it ready for the AppExchange.  The steps involved in this are a mystery to me right now – in between reading up on this I will also make incremental improvements including support for multiple languages and any minor feedback I receive from BETA testers.

Add Followers for Chatter allows any user to make any other user subscribe to a records Chatter feed.  Users related to the record, any user lookup on the record itself, will be recommended.  You may also lookup any other user in the application to allow them to subscribe to the records Chatter feed.

Once available in the AppExchange I will be looking to extend the recommendations including user lookups from related records and also Case and Opportunity Teams both individually and as a team.

If you see an application for Add Followers for Chatter in your organisation and would like to help me with testing in your Sandbox use this link to install a Beta version.

BETA Install Link:
https://test.salesforce.com/packaging/installPackage.apexp?p0=04tA0000000FAWt

UPDATE Sunday 3rd February 2013: The solution discussed in this post is no longer viable .  This is due to Visualforce now being delivered from a different domain and therefore causing xdomain issues interacting with the iFrame.  Two new solutions are discussed in a new post, ‘Two methods for embedding a Report or Dashboard Chart in Visualforce or Standard Pagelayout’

More than once I have wanted to display a report within my application. Unfortunatly there is not a Visualforce component for displaying a report directly on a custom Visualforce page. I used to fall back on linking to the report as a compromise. It is just that, a compromise, so I decided to break a cardinal sin of modern web design and decided to use an iframe.

This is the report I want to display in my visualforce page. By simply load this URL into an iframe however, I will get all the fluff that surrounds the report. The Headers with global search, the report title, the tag bar, the filter options etc. To remove these I will need to employ javascript.

Report to show inline

The following code is for the Visualforce page to display a simple iframe. The smart bit is in the Javascript. The Javascript displays the report in the iframe whilst hiding the fluff around the report making the whole experience that little bit nicer.

<apex:page>
   <script type="text/javascript">
    function renderReportFrame(myIframe, reportId, hideHeader, hideTitle, hideProgressIndicator, hideFilters, hideTagbar) {
        if (myIframe != null) {
            myIframe = myIframe.childNodes[0];

            if (myIframe != null) {
                myIframe.onload = function() {
                bPageTitle

                    var phHeader = this.contentWindow.document.getElementById("phHeader");
                    var tabNavigation = this.contentWindow.document.getElementById("tabNavigation");
                    var section_header = this.contentWindow.document.getElementById("section_header");
                    var progressIndicator = null;
                    var progressIndicatorList = this.contentWindow.document.getElementsByClassName("progressIndicator");
                    if (progressIndicatorList!=null) progressIndicator = progressIndicatorList[0];

                    var bFilterReport = null;
                    var bFilterReportList = this.contentWindow.document.getElementsByClassName("bFilterReport");
                    if (bFilterReportList!=null) bFilterReport = bFilterReportList[0];

                    var bPageTitle = null;
                    var bPageTitleList = this.contentWindow.document.getElementsByClassName("bPageTitle");
                    if (bPageTitleList!=null) bPageTitle = bPageTitleList[0];

                    if (hideHeader!=null && hideHeader==true) {
                        phHeader.style.cssText = "display: none;";
                        tabNavigation.style.cssText = "display: none;";
                    }
                    if (hideTitle!=null && hideTitle==true) {
                        bPageTitle.style.cssText = "display: none;";
                    }
                    if (hideProgressIndicator!=null && hideProgressIndicator==true) {
                        progressIndicator.style.cssText = "display: none;";
                    }
                    if (hideFilters!=null && hideFilters==true) {
                        bFilterReport.style.cssText = "display: none;";
                    }
                    if (hideTagbar!=null && hideTagbar==true) {
                        section_header.style.cssText = "display: none;";
                    }

                };
            myIframe.src = "/" + reportId + "?pv1=var1";
            }
        }
    }
  </script>

  <apex:outputPanel id="theReportPanel" layout="block">
    <apex:iframe id="theReportFrame" scrolling="true" />
  </apex:outputPanel>

  <script type="text/javascript">
      renderReportFrame(document.getElementById("{!$Component.theReportPanel}"), "00OR0000000gr45", true, true, true, true, true, true);
  </script>
</apex:page>

Update 12/10/2011: Vanessen Munisamy has just provided a better solution. Salesforce already provides a hidden URL parameter to handle much of the JacaScript from this post.

A simple thing you could have done is to append the parameter ?isdtp=mn to the url in the iframe source, the report will appear without headers. ex. /00OG0000004AIHR?isdtp=mn

This is great and should mean the solution is future better future proofreader against report output facelifts. However, I believe use of ‘hidden’ URL parameters is generally considered unsupported so is not guaranteed.

Hot out of a discussion on twitter including @aognenoff @botoscloud @joshbirk @wesnolte and of course a bit of echoing from darylshaber there didnt seem to be a clear answer – but it did confirm the platforms gap for a logging solution.

Challenge

How can we get a scalable logging solution for force.com.  My company uses a custom solution that is documented in this post however this isnt particularly scalable, it is siloed from our on-premise logging solution, splunk, and requires a decent amount of data storage – and precious DML.

Current Solution

The current solution we use at its very simplest required one custom object and two classes.  On top of this solution we also have scheduled batch apex class to purge the logfiles daily.

Custom Object: LogFile__c
Fields: Body__c (Long Text Area(32000) ), Type__c (Picklist)

Logger Class

public without sharing class Logger {

private String logType;
private String toAppend;

public Logger(String logType) {
this.toAppend = ”;
this.logType = logType;
}

public void log(String message) {

System.Debug(message);
toAppend = toAppend + ‘\n’ + System.now().format() + ‘: ‘ + message;
}

public void commitLog() {
try {
System.debug(‘Committing Log: ‘ + toAppend);
LogFile__c log = createLog();
if(!Properties.IS_TEST_METHOD){
insert log;
}
toAppend=”;
} catch (Exception e) {
System.debug(‘Failed to commit logs: ‘ + e);
}
}

private LogFile__c createLog() {
LogFile__c log = new LogFile__c();
log.Type__c = logType;
if (toAppend.length() > 32000) {
toAppend = toAppend.substring(0, 31999);
}
log.Body__c = toAppend;
return log;
}
}

public with sharing class LoggerFactory {
private static Map<String,Logger> loggers = new Map<String,Logger>();

public static Logger getLogger(String name) {
if (loggers.containsKey(name)) {
return loggers.get(name);
} else {
System.debug(‘…\n…\n…\nCreating new logger for ‘ + name);
Logger logger = new Logger(name);
loggers.put(name, logger);
return logger;
}
}
}

Example Trigger

trigger ContactBeforeInsert on Contact (before insert) {

Logger logger = LoggerFactory.getLogger(‘Contacts’);
logger.log(‘ContactBeforeInsert: ‘);
try {
logger.log(‘Calling [methodname], Number of SOQL: ‘ + Limits.getQueries());
//call method
} catch(Exception e) {
//handle error (we actually have an errorlog custom obj too)
logger.log(‘exception details ‘ + e);

}
logger.log(‘Finished ContactBeforeInsert, Number of SOQL: ‘ + Limits.getQueries());

logger.commitLog();
}

<span>%d</span> bloggers like this: