Monday, December 10, 2012

Quick tips for a successful deployment & user adoption: duplicate the dashboards


Introduction

User adoption is the key to a successful CRM project. Sugar will help you to accomplish this goal: Sugar flexibility and intuitiveness will let the end user feel like home. He may set up his CRM to look the way he wants: setting his home page drag & dropping dashlets, adding graphic reports, adding tabs has never been so easy.

But if you are working on a big deployment, you need to automatize tasks. However, some of them are not (yet) available out of the box in Sugar (and will never exist in Salesforce, he he): set the user password to a value and duplicate the dashboards. Both will be done programmatically. This is straightforward, no sweat.

Why setting a password? 
Sugar can generate a temporary password. The idea behind this is to leverage user adoption by setting human readable password that you will choose. Or it could for any other reasons:  e.g. setting the same password in different systems (you should then better consider Single Sign-On), setting an initial password that will be compliant with your corporate password rules.

Why setting dashboards?
For example you are launching a new CRM system for a call center and you want all the Support Reps to start with the same dashboard. And you might want to choose to freeze the dashboards and prevent the users from modifying them.

Assumption
You can access to your database with a client (gui or the command line).


Setting the password
The password is stored as a hash value in the database. The function that is calculating it is located on the User object. I highly recommended you to use the Sugar framework in order to stay upgrade safe.

Write a PHP file starting from the example below, save it to your Sugar instance root directory, log in the instance and execute the PHP file with your browser.


<html>
<head></head>
<body>
<?php
include ('include/MVC/preDispatch.php');
$startTime = microtime(true);
require_once(
'include/entryPoint.php');
ob_start();
require_once(
'include/MVC/SugarApplication.php');
$app = new SugarApplication();
$app->startSession();

$u = new User();
$u->retrieve('seed_sally_id');
$u->user_hash = User::getPasswordHash('sally');
$u->save();

echo 
"Password changed for Sally!";
?>
</body>
</html>


Alternative solution
Since you got the hash value using PHP, you may shoot a sql query:
update users
set user_hash='$1$rINuoIur$DhLu0b2AUgjrll7jX5bPP.' 
where user_name='sally';


Duplicate the dashboards
The dashboards user preferences are stored in the database, table user_preferences.
Before making changes, let's do a quick backup:
mysqldump --add-drop-database -u userdb -p'password' dbinstancename user_preferences > user_preferences-backup.sql

Then, create a new user, give him the expected role and set his dashboard. You might want to copy the preferences from an existing user, but this user should not use Sugar during the copy process.
When duplicating the user preferences, you will copy all the customizations: dashboards, list views, sub panels orders, etc.

Duplicating his preferences is easy, just 1 sql line:
insert into user_preferences(id,assigned_user_id,contents,category) 
select UUID(),'target user id',contents,category from user_preferences where assigned_user_id='source user id';

When duplicating the user preferences, you will copy all the customizations: dashboards, list views, sub panels orders, etc. To reduce the scope, add a "where clause":
insert into user_preferences(id,assigned_user_id,contents,category) 
select UUID(),'target user id',contents,category 
from user_preferences 
where assigned_user_id='source user id' and category like 'Home%';

A best practice would be to set the dashboard on a user and copy his preferences to a dedicated table. Let's create this table from the user_preferences:
CREATE TABLE `user_preferences_cp` (
  `id` char(36) NOT NULL,
  `category` varchar(50) DEFAULT NULL,
  `deleted` tinyint(1) DEFAULT '0',
  `date_entered` datetime DEFAULT NULL,
  `date_modified` datetime DEFAULT NULL,
  `assigned_user_id` char(36) DEFAULT NULL,
  `contents` longtext,
  PRIMARY KEY (`id`),
  KEY `idx_userprefnamecat_cp` (`assigned_user_id`,`category`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Copy the preferences to this table:
insert into user_preferences_cp(id,assigned_user_id,contents,category) 
select UUID(),'source user id',contents,category from user_preferences where assigned_user_id='source user id';

Then, when duplicating, use the new table:
insert into user_preferences(id,assigned_user_id,contents,category) 
select UUID(),'target user id',contents,category from user_preferences_cp where assigned_user_id='source user id';

Enjoy your new dashboards :-)

Wednesday, October 10, 2012

Add a Twitter flavor to your Sugar!

Following my presentation "Sugar + SocialCRM" during the CRM Acceleration in Paris last month, I presented quick & smart ways to bring cool Social interaction to Sugar. Sugar 7 will come with great features as well, but why waiting for April 2013. Start today!

In this post I will address how to quickly add a Twitter Feed based on the Twitter name of an Account or a Contact and a Twitter Search. In future posts, I will present the Facepile and the Twitterpile. Then the Klout integration, and the Google Map mashup.

The expected result will be getting an iFrame on the account page showing the Twitter identity of the Account and the last Tweets he posted:

And also a Twitter search based on the Account Twitter Name:

Let's start with a simple page!

We are working with the Account but the same behavior can easily be added to the Contact.

Step 1 
In the Sugar Studio, add a text field called twittername. This is basically the Account Twitter "screen name". Add the field to the page layout. Then I am assuming that someone in your company will fill the value for the account. How to find it? Just make a Twitter search with the account name, I guarantee you that you will find immediately the company Twitter account if they have one.

Step 2
Download JQuery.
In your Sugar root directory, create those directories 
/social/js/jquery/1.8.1
/social/twitter/minitwitter
Adapt the jquery version number to the version you have downloaded.

Step 3
I chose a JavaScript library called MiniTwitter (relying on JQuery):
Why JavaScript? Because this is the fastest way to retrieve the data from Twitter. The web server is delivering to your browser the page content, then your browser is taking care of the dialog with Twitter. The calls are asynchronous, so it will not slow down the rendering of the page.
MiniTwitter is very easy to use. See the examples on their front page. 

Wanna show the last Tweets from Lady Gaga on your blog? Ok, I know that you do not want, nobody wants it! Stop whining, let's choose a better example and get the lastest tweets from Simone Simons, the lead singer of the Dutch band Epica :)

JavaScript code:
$('.content_tweets').miniTwitter('SimoneSimons');


HTML:
<div class="content_tweets"> </div>

See the last 5 Tweets from Simone Simons:
Mini Tweets
Remark:
There is a fix to be made in MiniTwitter, file jquery.minitwitter.js:
line 132
obj.avatar = '<div class="tweet"><div class="avatar"><a '+rel()+' '+target()+' class="mt_avatar" href="'+obj.userUrl+'"><img src="'+obj.image+'" alt="'+obj.realName+'\'s avatar" border="0"/></a></div>';
 
add the width and height parameters to avoid surprises; Twitter might show very big avatars pics, you never know, so it is good to force the pic size.
obj.avatar = '<div class="tweet"><div class="avatar"><a '+rel()+' '+target()+' class="mt_avatar" href="'+obj.userUrl+'"><img src="'+obj.image+'" alt="'+obj.realName+'\'s avatar" border="0" width="48" height="48" /></a></div>';

Step 4
Let's create a very simple PHP page getTweets-simple.php to show the last tweet from any Twitter account:
<html>
<head>
<script language="javascript" src="/social/js/jquery/1.8.1/jquery.min.js" type="text/javascript"></script>
<script language="javascript" src="/social/twitter/minitwitter/jquery.minitwitter.js" type="text/javascript"></script>
<link href="/social/twitter/minitwitter/jquery.minitwitter.css" media="all" rel="stylesheet" type="text/css"/> 
<link rel="stylesheet" type="text/css" href="/social/social.css" />
<style type="text/css" media="screen">
  #custom-tweet-button a {
    display: block;
    padding: 2px 5px 2px 20px;
    background: url('https://twitter.com/favicons/favicon.ico') 1px center no-repeat;
    border: 1px solid #ccc;
    max-width:500px;
  }
</style>
</head>
<body>
<?php
$twitterName 
= (isset($_GET['twitterName']))?$_GET['twitterName']:'';
if (
$twitterName=='') {
    echo 
'<h2>Please provide a Twitter Name</h2>';
    echo 
'</body></html>';
    exit;
}
?><div class="tweets"> <div class="tweets_header">Mini <a href="http://minitwitter.webdevdesigner.com">Tweets</a></div> <div class="content_tweets"> </div> <div class="tweets_footer"> <a href="#"><span id="bird"></span></a> </div> </div>
<script type='text/javascript'>
jQuery(".content_tweets").miniTwitter("<?php echo $twitterName ?>");
</script>
</body>
</html>

Call the page: http://yourserver.com/social/getTweets-simple.php?twitterName=sugarcrm

Now let's write a get Tweets / search page
The page will take 2 parameters: TwitterName for retrieving Tweets from an account or search for making a search.
Let's add a cool box to enable the user to write a Tweet to the Twitter account or a new Tweet containing the search pattern as a hashtag.
And let's add a nice box at the top on the page to get all the basic information on the Twitter account.

PHP page getTweets.php:
<html>
<head>
<script language="javascript" src="/social/js/jquery/1.8.1/jquery.min.js" type="text/javascript"></script>
<script language="javascript" src="/social/twitter/minitwitter/jquery.minitwitter.js" type="text/javascript"></script>
<link href="/social/twitter/minitwitter/jquery.minitwitter.css" media="all" rel="stylesheet" type="text/css"/> 
<link rel="stylesheet" type="text/css" href="/social/social.css" />
<style type="text/css" media="screen">
  #custom-tweet-button a {
    display: block;
    padding: 2px 5px 2px 20px;
    background: url('https://twitter.com/favicons/favicon.ico') 1px center no-repeat;
    border: 1px solid #ccc;
    max-width:500px;
  }
</style>
</head>
<body>
<?php// input parameters: twitterName or search$twitterName = (isset($_GET['twitterName']))?$_GET['twitterName']:'';$search = (isset($_GET['search']))?$_GET['search']:'';
if ((
$twitterName=='')&&($search=='')) {
    echo 
'<h2>Please provide a Twitter Name or a search pattern</h2>';
    echo 
'</body></html>';
    exit;
}
$tweetbtn='';
if (
$search!='') {
    echo 
"
    <div align='center'>
    <form name='f1' action='getTweets.php' method='GET'>
    <input type='text' name='search' value='$search' class='inputText' />&nbsp;&nbsp;&nbsp;
    <input type='submit' name='btns' id='btns' value='search' class='inputBtn' />
    </form>
    </div>"
;
    if (
$search[0]=='#') $search = substr($search,1);
    
$tweetbtn="?button_hashtag=$search";
}
if (
$twitterName!='') {
    echo 
"<div class='tweets' id='infoUserAccount'><div align='center'><img src='/social/twitter/wait.gif' width='48' height='48' border='0' /></div></div><br/>\n";
    
$tweetbtn="?screen_name=$twitterName";
} 
?><div class="tweets"> <div class="tweets_header">Mini <a href="http://minitwitter.webdevdesigner.com">Tweets</a></div> <div class="content_tweets"> </div> <div class="tweets_footer"> <a href="#"><span id="bird"></span></a> </div> </div>

<script type='text/javascript'>
<?php 
if ($twitterName!='') { ?>jQuery(".content_tweets").miniTwitter("<?php echo $twitterName ?>");
var url = '/social/twitter/getUserInfoTwitter.php?twitterName=<?php echo $twitterName ?>';
$.get(url, function(data){
document.getElementById('infoUserAccount').innerHTML='<b>' + data.name + '</b><br/>' + 
    '<a href="https://www.twitter.com/#!/'+data.screen_name+'" target="_blank">@' + data.screen_name + '</a><br/>' + 
    data.description + '<br/>' + 
    data.location + '<br/>' + 
    data.statuses_count + ' Tweets<br/>' + 
    data.friends_count + ' Following<br/>' + 
    data.followers_count + ' Followers<br/>' + 
    data.listed_count + ' Listed<br/>';
}, "json");
<?php } else { ?>jQuery(".content_tweets").miniTwitter({query: "<?php echo $search ?>"});
<?php } ?><br/>
</script> 
<div id="custom-tweet-button" align="center">
  <a href="https://twitter.com/intent/tweet<?php echo $tweetbtn ?>" target="_blank">Tweet</a>
</div>
</body>
</html>

In order to get the information on the Twitter account, we are making another asynchronous call to Twitter using JQuery. The page is called getUserInfoTwitter.php and it is doing a REST call to Twitter. E.g. the call http://api.twitter.com/1/users/show/sugarcrm.json will return in a JSON format the information on the Twitter account SugarCRM. But for a cross scripting security issue, you cannot address directly Twitter, you are calling a PHP script located on your server.

getUserInfoTwitter.php
<?php
 function loadURL_cURL($ip,$uri, $timeout){
   
$header=array("Host:".$uri);
   
$ch = curl_init();
   
curl_setopt($ch, CURLOPT_URL, $ip);
   
curl_setopt($ch, CURLOPT_HEADER, false);
   
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
   
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
   
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
   
$return_data = curl_exec($ch);
   
curl_close($ch);
   return 
$return_data;
}
$twitterName = (isset($_GET['twitterName']))?$_GET['twitterName']:'';if ($twitterName=='') exit;
// get informations on the user
echo loadURL_cURL("http://199.59.149.232/1/users/show/".$twitterName.".json", "api.twitter.com", 30);?>

Remark
you should not use this exact syntax. I am meeting important DNS resolution failures in my Linux box so I decided to hard code the IP address in the call, which is a worse practice.

You should replace with this code:
<?php
$twitterName 
= (isset($_GET['twitterName']))?$_GET['twitterName']:'';
if (
$twitterName=='') exit;// get informations on the user$url = "http://api.twitter.com/1/users/show/".$twitterName.".json";$userInfo = curl_init();curl_setopt($userInfo, CURLOPT_URL, $url);curl_setopt($userInfo, CURLOPT_RETURNTRANSFER, TRUE);$userInfo = curl_exec($userInfo);echo $userInfo;?>


Now you might want to call the page to get the SugarCRM Twitter account page.
http://yourserveur.com/social/twitter/getUserInfoTwitter.php?twitterName=sugarcrm
On the top, you get the twitter account information, then the last 5 tweets:
 If you click on the Tweet link at the bottom, you get the usual new Tweet page, hosted by Twitter, with the recipient pre-filed:


Let's make a search with the pattern SugarCRM.  
http://yourserveur.com/social/twitter/getUserInfoTwitter.php?search=sugarcrm
On the top, you get the search box, you may change the pattern:

If you click on the Tweet button, you are sending a Tweet with the pattern as a hashtag:

Ok, that's nice, now I want this cool features in my Sugar!

This is easy :) Let's create 2 iFrame fields. The first one for the account Tweets, the second one for the search.

The Tweets field
Type: iFrame
Name: tweets
Generated URL: checked
Default value: http://yourserveur.com/social/twitter/getUserInfoTwitter.php?twitterName={twittername_c}
Height: 400



The Tweets Search field
Type: iFrame
Name: tweetssearch
Generated URL: checked
Default value: http://yourserveur.com/social/twitter/getUserInfoTwitter.php?search={twittername_c}
Height: 400


Create a new tab for each field, and test.

Now this is your turn to play :D
Let me know how you are doing!

Monday, October 8, 2012

Slideshare - my last public presentations

My last public presentations are available on http://www.slideshare.net.


Sugar + SocialCRM - SugarCRM Acceleration Paris - 13 Sep 2012
Presentation delivered in Paris (French)




SugarCRM edition communautaire vs commerciale
Webcast (French), the recording will be posted soon to the SugarCRM web site.

Open World Forum 2012

Hi, sorry for not blogging those last (very busy) days.

I will attend a panel on Thursday 11th October at the Open World Forum in Paris. The topic is "Open Source Web Application on The Cloud".
Links:
The Open World Forum web site,
my session.

And I will post asap the source codes I presented during my session "Sugar + SocialCRM" at the CRM Acceleration in Paris on 13th September.

Wednesday, May 9, 2012

Pimp my mac!

Last day I found a nice website: Etsy.com. This is a marketplace where you can buy handmade objects: art, plates, dolls, candles, and... MacBook decal! The choice is amazing. Most of the designs are using the apple logo. As the decals are produced on demand, you may ask for a custom size to fit exactly your laptop size.

Here are some samples:

 Iron man
 Super Mario
Zombie Snow White
Yoshi



I bought this one:


Next time you will attend one of my presentations, check out my laptop decoration :)

Tuesday, May 8, 2012

SugarCon 2012

SugarCon ended on the 4/26. This is the annual SugarCRM event which occurs in April, celebrating at the same time the company anniversary. Happy B'Day SugarCRM!

The week end before the conference I ran the Diablo Trail Run 50K (7,000 feet of climbing and only 4 aid stations). Or should I say, "I walked". I still got jet lag and was recovering from an ocular infection (caught 2 days ago in the plane) healed with an expensive antibiotic drugs. The heat was terrible all long the day. I made my way walking more than running, but finished the race!

The diablo trail run 50k at the start line

On Monday Larry Augustin (our CEO) opened the Partners day keynote in a full room :)

Larry

On a big screen, a live search showed the tweets with the hash tag #Scon12 :
 
Lila Tretikov and Clint Oram provided an overview of SugarCRM 6.5 new features (new UI, more powerful search, calendar improvements) and presented the roadmap.
 Lila & Clint (photo by Bill Harada)

SugarCon was the opportunity to remind that we have a strong global partnership with IBM and that SugarCRM is integrated with the IBM stack (Lotus Notes, Lotus Live, Sametime, Domino, Cognos, etc.)
 

The event was a nice opportunity to catch up with colleagues and partners from all over the world.
 
Working session between Samuel (Ysance), Vincent & Stephane (Synolia)

SugarCon brought a lots of great presentations and high quality speakers: Guy Kawasaki, Paul Greenberg, Dr. Natalie Petouhoff and Paul Gillin.


The Tuesday evening an awesome party at the Exploratorium gathered customers, partners and Sugas (nickname for SugarCRM employees). The place is an interactive science museum. Everybody enjoyed the friendly mood and the rock band.
The Exploratorium (photo by Bill Harada)

Me and Nana from OSSCRM (photo by Bill Harada)


Erin & Jan enjoying the party at the Exploratorium

Guy Kawasaki launched the third day with a funny keynote; then at the break he signed his book "Enchantment" for the attendees.

Guy Kawasaki (photo by Bill Harada)


On Tuesday & Wednesday afternoon I gave a presentation during the partner training with Bruce Sinclair and Erin Fetsko. You already guessed the topic, right? Yes, it was dealing with the "Roach Motel" of cloud services...

If you missed it, I am giving three webcasts in May, more information and registration: http://tortuga-crm.blogspot.fr/2012/04/sugarcon-presentation-and-webcasts-why.html

On the Wednesday afternoon, Jan Sysmans presented the App Throwdown: six integrations were demo’ed in a three-minute window for each!
Jan Sysmans (photo by himself)

If you were not able to make it this year all the slide decks are available here:
http://sugarcon.sugarcrm.com/program/session-schedule 

Next year, SugarCon will happen on the East Coast in New York! Save the date:

Friday, April 13, 2012

SugarCon presentation and webcasts: Why SugarCRM is a better choice compared to Salesforce.com

Today I am very excited to announce that I will present my last deck "CRM Made Simple: Why SugarCRM is a better choice compared to Salesforce.com" at SugarCon and will make three webcasts. 

Webcast description
In today's competition every company needs a CRM to grow and secure its business. But comparing CRM vendors solutions is a tough nut to crack if you are not a CRM expert. This webcast will help you understand why SugarCRM is a better choice than Salesforce. If you are choosing your first CRM solution, thinking about making a swap or just curious, you should attend this presentation.

SugarCon
http://sugarcon.sugarcrm.com/program/session-schedule
Date TBC. Tue 4/24 or Wed 4/25, during "Partner traning: Sales 101" (1:30-5:30pm)

Webcast Emea - in English
Date & Time:
Wed, May 9, 2012
6 am PT / 14:00 GMT / 15:00 CET
Duration: 1 hr
Register for this webcast  

Webcast Emea - in French
Date & Time:
Fri, May 11 2012
10:00 CET
Duration: 1 hr
Register for this webcast

Webcast North America - in English
Date & Time:
Wed, May 16 2012
9 am PT / 12 pm ET
Duration: 1 hr
Register for this webcast


SugarCon is going to be an amazing event! If you are attending SugarCon, please download the SugarCon app (iOS or Android devices - key word search SugarCon). The app will help you to build your agenda and brings a lot of nice social features. Check it out! And feel free to add me in your friends list - and win cool badges ;-)