Bibiano Wenceslao http://bibianowenceslao.com UX, Social Media, Customer Support & Workflows Sun, 17 Aug 2014 16:09:46 +0000 en-US hourly 1 http://wordpress.org/?v=3.9.2 5 Ways To Maximize Sales By Improving Product Pricing http://bibianowenceslao.com/5-ways-to-maximize-sales-by-improving-product-pricing/ http://bibianowenceslao.com/5-ways-to-maximize-sales-by-improving-product-pricing/#comments Sun, 17 Aug 2014 15:38:56 +0000 http://bibianowenceslao.com/?p=1087 This content was originally found at: 5 Ways To Maximize Sales By Improving Product Pricing

If your competitors are selling better even when you have the cheaper and better service or product, you might be doing pricing wrong. There is psychology behind product pricing, and here are ways to make your product prices right to maximize your sales.

For more great content like this go to: Bibiano Wenceslao

]]>
This content was originally found at: 5 Ways To Maximize Sales By Improving Product Pricing

We basically come up with prices based on the following factors:

  • Production cost
  • Reasonable profit
  • Market competition

But if your competitors are selling better even when you have the cheaper and better service or product, you might be doing pricing wrong.

There is psychology behind product pricing, and here are ways to make your product prices right to maximize your sales:

1. Anchoring

Anchoring is rawly defined as “a cognitive bias that describes the common human tendency to rely too heavily on the first piece of information offered when making decisions.”

Sometimes an expensive product is perceived as premium or top of the class at the first impression – the concept of perceived value based on price. An item would be priced so ridiculously high so that the customer would think he got a good deal by opting to a presented cheaper option. These items are usually put side-by-side for immediate comparison.

Some retail stores do so well on this by putting products “on sale”. Items are actually just priced higher than they are originally to compensate for the “markdown”. For example, an item on sale at 20% off is priced $100, when originally it was just $80.

This can work on customers who haven’t tried the product yet, nor its competitors, and haven’t done research on the market. This is also effective if you have complete monopoly of your product market.

2. Bundling

Think of a “smartphone + smartwatch” bundle. Smartwatches may be new to most smartphone users, hence an expected low increase in sales. To cope with this, smartwatches are being sold together with smartphones but in a “cheaper” price altogether e.g. “save more than 30% off on the X smartwatch when you buy the X smartphone bundle” when in fact even with the 30% markdown, the original total price for both items is already compensated.

This approach also works well on products that aren’t selling well, or anymore. If you have a high-selling product and a low-selling one, try bundling them together. The profitable one can help in introducing the low-selling product to the market, and at the same time the latter (which can also be offered as a freebie) can help increase the sales of the high-selling one.

3. Availability

Putting limits to product availability or access can influence a customer’s purchasing decision.

For instance, putting items on sale for a limited time or quantity makes the customer think he might run out of time, or may not be able to buy the product due to shortage if he doesn’t hurry.

4. Staggered Pricing

Let’s say you’re selling a software license or offering a service at $300 yearly – quite a big amount on the first impression, right? Divide that same price by the number of months, and stating it’s just $25/month would make it be perceived as more affordable.

To make it better, you can put up an annual but discounted price e.g. “get 2 months free when you subscribe to our annual plan”.

5. Whole Numbers

Disclaimer: This section is debatable, and it’s just my own opinion.

I perceive 11.50 to be cheaper than 11.45 or 11.49. Saying it’s just 11.5 (sans the hundredths zero) is also better. Best? Saying it’s just 11 or 12 but not 11.00 or 12.00 (nor 10.99 or 11.99).

State your price in its shortest possible form. Eliminate the decimal point.

What do you think of these methods? Do you have your own approaches in pricing? Let me know your thoughts in the comments section down below.

For more great content like this go to: Bibiano Wenceslao

]]>
http://bibianowenceslao.com/5-ways-to-maximize-sales-by-improving-product-pricing/feed/ 0
Getting Back On Track http://bibianowenceslao.com/getting-back-on-track/ http://bibianowenceslao.com/getting-back-on-track/#comments Tue, 22 Jul 2014 07:45:38 +0000 http://bibianowenceslao.com/?p=1064 This content was originally found at: Getting Back On Track

It's been more than a year since the last time I wrote something here. Yeah, a good question would be: what happened? Well, a lot.

For more great content like this go to: Bibiano Wenceslao

]]>
This content was originally found at: Getting Back On Track

It’s been more than a year since the last time I wrote something here. Yeah, a good question would be: what happened?

Well, a lot.

An earthquake and a typhoon

In October of 2013, a 7.1 magnitude tremor occurred in the Central part of the country. A month after, typhoon Haiyan (locally known as “Yolanda” – one of the strongest storms recorded on the planet) hit at a neighboring location where the quake struck. More important than anything else, lives and homes were lost.

On a lighter note these disasters also called for expected power outages and lost of internet and mobile connectivity. A lot of establishments were forced to temporarily close for renovation due to structural damages. Some that rely heavily on technology were slowed down, a few put to a halt. For a while we got used to rotational brownouts.

With a job that relies heavily on being online, emails of excuses and apologies for delayed projects were sent at those times. Though very thankful that family and friends were safe, career-wise I was starting to get in pretty bad shape.

Enter, stress

As my motivation dwindled, I got lazier, stopped learning, and got fat. I started to play computer games more than doing any work or studying. I got the habit of putting off tasks beyond deadlines. At this point, I was in absolute bad shape, burned out.

So who’s to blame?

Myself. I was the embodiment of stress in its final form – the way I look at it. I wanted to do and have everything, without even trying to do anything. I began finding reasons to put the blame on others about my own mistakes. I got easily angry, greedy, jealous and close-minded. It felt awful being awful. I wasn’t helping myself, nor was I of any help to others, particularly to my family and SO.

I got out of the sinkhole

How? Eventually I hit a proverbial brick-wall, and a realization dawned on me: I wasn’t going anywhere. Not forward. Not up. I had people beside and behind me hurting.

With my SO’s help, I started with regaining my spirituality – the kind of meditation we get to do together. She was right: I was too focused on work and money. My family was very understanding and supportive too as I stopped earning. Well, about that…

I quit project-based jobs

Not technically since my job now transitioned from being a project-based job, but I decided to call off other projects, stopped getting new ones and just focus on one. I’ve had a number of good clients. Telling them that I was leaving was hard. This also means starting with a lesser pay than what I was used to.

But it was a good decision, probably the best I ever made.

I now work as customer/technical support at a bootstrapped startup niche website design solution company directly with the founder. Beyond being an employee, I get to experience being an apprentice as he not only provides guidance but lessons as well – the things you will never learn reading articles or watching videos for years.

I got married

To the same woman who helped me through it all. Our story may not have been close to any fairy tale, but if a woman stays with you, and helps you at your worst – wife her.

Player 3 has entered the game

Yes, a baby’s on the way. But I’m more comfortable now with handling a lot of things in sync. Besides helping my wife with house chores, driving her to work, getting her home, making preparations for the upcoming kid and doing a job I love – I still get to find time for myself working on personal projects, continually honing my skills, getting new ones, and even play DotA 2 for a few hours on weekends.

To sum it all

As cliché as this is, we are our own worst enemies.

  1. Work life - Don’t try to do everything at once. Don’t overwork yourself. You don’t want to spend the same money you worked hard for on medications just to get you back to health. Take a vacation.
  2. Social life - Get out of the house. Treat yourself and others. Socialize in person, not just on social media. Attend events. Spend time with your family and friends.
  3. Mental health – Get into meditation, whatever works best for you. Don’t overload yourself with information.
  4. Physical health – Get some exercise. Have a healthy diet.
  5. Emotional health – Seek help when things feel spiraling out of control. Be transparent of your situation, not only with people you work with but also to those whom you matter the most to.
  6. Time – You can only do so much with your time. Before deciding to do something, ask yourself: will this make the future better both for me and everyone around? If yes, then do it.

For more great content like this go to: Bibiano Wenceslao

]]>
http://bibianowenceslao.com/getting-back-on-track/feed/ 0
SERPs Competitor Scraper – Quick Raw Analysis Using Excel http://bibianowenceslao.com/serps-competitor-scraper-excel-tool/ http://bibianowenceslao.com/serps-competitor-scraper-excel-tool/#comments Fri, 07 Jun 2013 11:33:05 +0000 http://bibianowenceslao.com/?p=917 This content was originally found at: SERPs Competitor Scraper – Quick Raw Analysis Using Excel

Keyword research isn't just about search volume. Knowing which terms you should give priority when optimizing can help save you time and brain juice writing good content.

For more great content like this go to: Bibiano Wenceslao

]]>
This content was originally found at: SERPs Competitor Scraper – Quick Raw Analysis Using Excel

Keyword research isn’t just about search volume. Knowing which terms you should give priority when optimizing can help save you time and creative juice writing good content. But competitive analysis can be a daunting task, even intimidating for the non-savvy.

SERPs Competitor Scraper

Quite the irony, I went to close my Google AdWords keyword tool window even before starting to search for the right keywords I could use for this post. You might not want to don’t do the same. I was just in a hurry.

This was supposed to be a followup or a supplementary piece to the do-it-yourself keyword research guide I’ve recently started drafting for my DIY Online Marketing – From Zero to Something series which you might want to subscribe to.

The Original SERPs Analysis Tool

Way back in May 2011, Tom Anthony wrote a post on SEOmoz (now Moz) about quick SERPs competitive analysis using Google Docs. It was a robust yet simple-to-use tool. If you’re looking into serious competitive analysis and don’t like to use or install Excel on your machine, I recommend using his spreadsheet instead of mine.

There was this problem I had though with using the ImportXML function which powers all the SERPs scraping work. Google Docs sets request limits for it – a maximum of 50 requests per spreadsheet document – if I’m right. Often times I get errors on my end.

ImportXML function limit on Google Docs

I understand why they had to set limits – to prevent abuse. This was probably the same reason why Tom had set a maximum of 50 keywords to be used.

I also found Google Spreadsheets to be a bit slow on updating computed values whenever changes on referenced cells are made. Not everyone has decent internet connectivity speed including me which makes cloud computing a bit painstaking than productive.

A Faster but Stripped-down Alternative

These issues led me to come up with an Excel equivalent. I’m not good with writing scripts so I had to stick with mixing up Excel functions, simple macro recording and an indispensable Excel add-in I’ve been using since knowing about its existence.

Setup

Before anything else, for you to be able to use the spreadsheet tool make sure you have the following:

  • Microsoft Excel 2007/2010macros must be enabled to run
  • Niels Bosma’s SeoTools for Excel Add-in – you can download it here and properly follow the installation procedure (I recommend doing Method B: Permanent Installation);

You may then download the Excel spreadsheet as linked below:

Download SERPs Competitor Scraper (~350 KB)

How to Use

I have become a fan of procedure documentation and nothing makes it easier than SweetProcess. It’ll be impractical for anyone to go through this whole blog post repeatedly (or refer someone else here) just to know how to use the spreadsheet tool so I’ve made a SweetProcess procedure linked below:

Procedure: Using the SERPs Competitor Scraper

How It Works

On the Keywords sheet is a column (C) containing Google search query strings assigned to each keyword. These strings also rely on the Query String value set on cell D1.

Google search query for each keyword

Making use of the XPathOnURL function of the SeoTools add-in, results on every top 10 positions of the SERPs for every keyword is scraped.

Search results are scraped

The scraped results are then arranged by SERPs position.

Scraped results are grouped by SERPs position

All scraped strings are then cleaned, leaving only the website domain for each cell value.

URLs are cleaned

All unique domain values are then consolidated into a single list and assigned scores according to their SERPs frequency and position. For the score computation, I multiplied the CTR rate of a position with the domain’s frequency on that same position.

Unique URLs are consolidated and scored according to SERPs position

With the domain list and computed scores ready, websites are then arranged in descending order by score using a simple recorded macro.

URLs are listed and arranged in descending order by score

Scoring

I just came up with the scoring system I used based on Dejan SEO’s 2011 SERP CTR data. I may have misused or misinterpreted their findings. If you wish to use an alternative approach, Stephen Croome suggest just using domain frequency on the SERPs.

What it Lacks

As I mentioned earlier, Tom’s version is better than mine when it comes to analysis. His spreadsheet tool is also able to do everything what my version can. I’m not well-versed with APIs so I’m not yet able to figure out how to properly fetch data from Mozscape which would have made this spreadsheet akin to the Google Docs version.

Possibilities

Like every other tool, there’s always room for improvement. I’m planning to rework the Keywords sheet and add a new column beside the entered keywords that would contain keyword difficulty scores based on how strong are the top X websites competing for each of those term.

Please feel free to dissect the whole Excel workbook and modify it to suit your preference. You can even refer to the following articles for ideas on making possible improvements to it:

If you’d like to get updated on the changes to this tool as soon as I publish them, you can subscribe to my DIY Online Marketing series. I barely write, but when I do I make it a point to make the piece worth reading (despite my limited English vocabulary – I get stressed when I write *lol*).

Hope you enjoyed this post and the Excel tool! You can subscribe to my feed. I’m also on Twitter and if you feel like asking something. I also secretly want to be your friend.

For more great content like this go to: Bibiano Wenceslao

]]>
http://bibianowenceslao.com/serps-competitor-scraper-excel-tool/feed/ 9
22 Must-Have Chrome Extensions for Productivity (I Think) http://bibianowenceslao.com/must-have-google-chrome-extensions-for-productivity/ http://bibianowenceslao.com/must-have-google-chrome-extensions-for-productivity/#comments Sat, 28 Jul 2012 08:10:18 +0000 http://bibianowenceslao.com/?p=429 This content was originally found at: 22 Must-Have Chrome Extensions for Productivity (I Think)

Working fast enough already? Maybe you aren't yet. Maybe you should check out some of these Chrome extensions to boost your productivity.

For more great content like this go to: Bibiano Wenceslao

]]>
This content was originally found at: 22 Must-Have Chrome Extensions for Productivity (I Think)

Sure, I’m probably bad at coming up with catchy titles, but the following Google Chrome extensions I’ll be sharing with you are going to change and improve the way you’re able to do things around the web – may it be at work or just merry-browsing cat pictures (pow! right in the productivity).

While gathering ideas for a write-up about productivity, I found this post about Chrome extensions for link builders by Jon Cooper (his blog is a link building treasure cove by the way – check it out!). He made a great roundup of extensions, and some of it I just knew since I stumbled on that post. There are some though that I’ve been using but are not listed there. Since this is my online journal, I decided I might as well create a list of my own – not just for work purposes, but how I function around the web in general.

Time-savers

(1) Pasty and/or (2) A Href++ and/or (3) LinkClump

These are all multiple URL openers, simply defined, but each has a feature different and/or not found on the other two.

I’ve been using Pasty since discovering it while looking for a simple multiple link opener. If you’re like me who keeps a lot of windows open (Notepad++, Excel, Google Docs, etc.) at a time while working, and tends to cross-copy-and-paste data between applications (think Excel to browser), Pasty is a necessity. To use it, just highlight and copy the group of URLs you’d like to open at the same time (works even when you’re outside the browser like on Excel, Word, or a simple text editor) and then click on the Pasty extension button to open all those up in separate tabs.

The downside of Pasty though is that it can’t detect incomplete URL formats (e.g. without the “http://”). This is where A HREF++ excels at. Simply highlight the group of links, right-click and then select A HREF++ in the menu. I have also noticed that the URLs still have to start with a “www.” for this extension to work, otherwise it just won’t open the link up.

But what if the link is anchored to a text or an image?

It’s Linkclump to the rescue. Simply press and hold the right mouse button while dragging it around the anchor elements, highlighting them, and then releasing it.

Depending on how you go about your everyday tasks, you might find using just one or two of them enough, or if you’re like me, just have the three ready by your side.

(4) 1Password

I have a lot of online accounts, and sometimes trying to remember what password I use for each is like trying to recall the name of your seatmate in 6th grade. Luckily, there’s 1Password to deal with forgetfulness. It is offered in both free and paid versions. The browser extension actually works in sync with a local installation on your machine as well as with installations on other platforms that you’ve connected through your account. It’s currently available for Windows, OS X, iOs, and Android. Well, a few seconds saved from normally typing your unguessable password is a few seconds back into your life.

(5) Evernote Web Clipper

Evernote is another extension-software combo that allows you to save or archive documents, web pages, images, and other sorts of file attachments, and everything’s synced across all devices you’ve connected through your own Evernote account. I personally use this as a bookmarker and a to-do lists organizer. To learn more about this amazing tool, read Joe Brockmeier’s post on tips for using Evernote effectively.

(6) Imgur

This nifty Google Chrome extension allows you to take a screenshot (an area you can define on the page you’re on, the whole currently viewable area, or the whole page itself), then automatically uploads the image to imgur.com (and saves it to your imgur account if you have one though not necessary), and provides you with a URL to the image. Excellent for quickly sharing screenshots with others.

(7) Insert Text

As Jon said, if there’s a snippet of text you’re using repeatedly, this extension will save you a good amount of time. Simply right-click inside the the input box of the form you’re filling up, choose “Insert Text”, and then choose the specific text block you’ve defined and saved earlier inside the extension’s options panel. Also great for automatically inserting pre-configured email opening/farewell compliments.

(8) Word Count

I’m not sure how you’ll be using this in some other ways, but I’m likely using it similar to Jon’s strategy (work-related). Just highlight the block of text on the page and click on the extension button to know the number of words and characters currently selected. Word Count is also useful when you’re filling up those web forms that have set character limits for inputs but doesn’t distinctly show you the current number of characters you’ve typed in.

Email

The following two Chrome extensions are just gloriously awesome. If you’re a Gmail user (why, if you’re not, you should be – trust me!), these extensions will make your life better you probably can have a dog as a pet right after or grow tomatoes on your backyard.

(9) Rapportive

After installing it on your browser and setting it up with your Gmail account, something  similar to an author box appears on the right side of your screen every time you’re composing a new message or writing a reply. Rapportive detects the recipient’s email address and gathers data from around the web including social profiles and then returns those results to you. Sometimes, a Twitter mention seems to have a higher chance of getting a reply than an email, right?

(10) Boomerang

Another Google Chrome extension that integrates with the Gmail app, Boomerang allows you to send an email at a designated time (send later) or back to your inbox (e.g. if the recipient doesn’t reply in a day or two).

I suggest you check out John Doherty‘s post about Gmail productivity setup for link builders (even if you’re not a link builder) at the SEOmoz blog to know more about these must-have extensions. Great, great tips on being a Gmail ninja from John!

Notification

Some say notifications are counterproductive. Instead of keeping your focus on completing a particular task, those little conspicuous numbers scream at you “hey, it appears you’re very busy, but click me!”. The following are exceptions though.

(11) X-Notifier

It’s basically a mail notifier for Gmail, Hotmail, Yahoo, AOL and other web mail services (though I’m more of a pure Google user). X-notifier is great if you’re a hustler – always making sure that every task gets acted upon as soon as you receive it. Imagine an urgent email from your superior or a colleague. Also really advantageous when you’re just waiting for a reply from someone and quite an essential if you have multiple email accounts.

(12) Google Reader Checker

I usually just turn the number of unread posts notification off while working. But when it’s reading/free time, I have the number of unreads on the Google Reader Checker on as I always make sure I’m able to squeeze into my head as much as I could read. Also great when you just want to catch up on the latest buzz, and hey, life’s easier when things are just a click away.

Work-related

Not everyone may be using the following extensions, but for people in the same field as I am, these are necessities.

(13) SEOmoz Mozbar

Aside from the overlay feature this extension integrates into the SERPs, the array of functionalities this tool offers (instant page analysis, link type highlighting, IP address and country information, and quick links to other tools for deeper analysis) makes it indispensible. Though, as I would recommend being a PRO member gives you invaluable perks, the free version of the Mozbar has features good enough for those who are just starting out.

(14) Domain Hunter Plus

I knew about this extension when Jon shared about it on one of his blog post calling it the next generation checker. While he clearly outlined how to use this exceptional tool to check on dead links, get further information about the domains (also making it easier to get the domain registered in the process) as well as the export to CSV feature, it lacks the ability to directly show the user which of the links on the page contains the dead URL unless of course if the URLs aren’t anchored to a text string or an image and are written as is. Well, I may have missed or really don’t know if Domain Hunter Plus has that feature, but as far as I know it doesn’t. Nevertheless, this tool is a keeper and a combo with the next one makes both an integral part of an uber broken link building arsenal.

(15) Check My Links

What Domain Hunter Plus lacks in visual identification of dead links, Check My Links makes up for. Jon again (I told you to check out his blog) generously shares about this great Chrome extension on his post at the SEOmoz blog with bonus tactics you can readily follow and assimilate into your current link building strategy.

(16) NoFollow

Although the Mozbar has this link highlighting feature already, I find Nofollow does a better and cleaner job at the task. I could just leave it on without it being visually obtrusive as the dotted lines look more cordial to me than the colorful highlights the Mozbar creates that often times moves on-page objects around by a few pixels. How is this useful? Well, if you’re going around sites checking for opportunities on pages your site could get listed, nofollowed links becomes a decision factor.

(17) Scraper

I knew about this extension on Justin Briggs‘ blog post about quick link prospecting using it. Though it requires a quick lesson about the basics of using XPath, the Scraper significantly turns the SERPs into a link builder’s bountiful sea of links.

(18) Firebug Lite

I started using this extension when I got hooked on learning how to create WordPress child themes. Though not as feature-packed as its Firefox ancestor-counterpart, Firebug Lite has all the required functionality I exactly needed in tweaking website layouts and learning about how other sites are structured inside-out.

Others

(19) Goo.gl and/or (20) Bitly

Both are URL shorteners but one does something the other can’t (as far as I know). With bitly, you can add notes to the URL you just shortened and bookmark “bitmark” it. I’m not sure if goo.gl can do the same.

(21) Google Dictionary

With this Google Chrome extension, you can view definitions easily as you browse the web. Double-click any word (while pressing a trigger key like Alt if you’ve set it to that to avoid conflict with other keyboard/mouse functionalities) to view its definition in a small pop-up bubble. You can also query the complete definition of any word or phrase using the toolbar dictionary. Being a non-native English speaker, the Google Dictionary comes in very handy for me. I cannot fathom not being able to comprehend what others say. Fathom. It sounds cool. It’s like…Gotham. Okay, it’s not. But see, it’s useful for me.

(22) Adblock Plus (Beta)

Before I started studying in this field, I used to turn this extension on all the time. It was when I began reading about PPC, on-page optimization and affiliate advertising that I just rarely use this. I had to understand how paid ads work (particularly in the SERPs), how people use and perceive them, how it affects the overall appeal of a page (like those sites within the Google Display Network), among other things that involves online advertising. If you don’t care about those though and are just sick of the popup and sliding ads on sites you frequently visit, you’ll love AdBlock Plus. I understand, you’re clearly not the 1,000,000th visitor who just won a gazillion dollars.

Alternatives

Do you know of better ones that can possibly substitute what I already have in my list? Is there another great Chrome extension you think I should check out? Do you think I’ve been using any of the extensions incorrectly? It would be great if you could tell me about it in the comments section below.

Conclusion

No matter how good the tools you use, success will always depend on self-discipline and good time management skills. Just get things done. The cats can wait.

Related Links

If you liked this post, you can subscribe to my feed. I’m also on Twitter and if you feel like asking something. I also secretly want to be your friend.

For more great content like this go to: Bibiano Wenceslao

]]>
http://bibianowenceslao.com/must-have-google-chrome-extensions-for-productivity/feed/ 3
How to Create Copies of a Google Docs Collection/Folder, Even Ones Just Shared With You http://bibianowenceslao.com/how-to-create-copies-of-a-google-docs-collection-or-folder/ http://bibianowenceslao.com/how-to-create-copies-of-a-google-docs-collection-or-folder/#comments Sat, 16 Jun 2012 11:29:52 +0000 http://bibianowenceslao.com/?p=239 This content was originally found at: How to Create Copies of a Google Docs Collection/Folder, Even Ones Just Shared With You

Ever wanted to create copies of whole collections in Google Docs? Your Google Docs files in the cloud are synced with your local Google Drive directory ala Dropbox. Learn more.

For more great content like this go to: Bibiano Wenceslao

]]>
This content was originally found at: How to Create Copies of a Google Docs Collection/Folder, Even Ones Just Shared With You

Yesterday after writing a number of documents and grouping them into collections and subcollections, I thought I should make a backup of everything since I’ll be sharing those files with other people. To my surprise, Google Docs simply can’t do that. Even making copies of several documents seemed to be very tedious (imagine a hundred files) considering each had to be individually opened before getting access to the File menu where you’ll find the Make a copy option.

I tried to google on how to create backups of Google Docs files in the cloud and to no avail only found these two Google Product Forums threads being the closest to my query, but both still left unsolved as of this writing. Downloading the whole collection, renaming it, and uploading it back? Maybe the GDocs team already has this functionality set for the next update.

To cut the supposed-to-be short story shorter (I’m sorry you had to read through my harrowing narrative), I realized I’ve installed Google Drive locally on my machine. Then remembering that all my Google Docs files in the cloud have been moved to Drive and that everything would just sync ala Dropbox…Eureka! Come, let’s jump right into the process.

Update: Using the same procedure, you can also create your own copy of any document or folder that you don’t own but were just shared to you as long as you have the “Can Edit” permission to it. Sharing settings though won’t be replicated in any copy that you create.

Google Drive Installation and Signing In

Make sure you already have Google Drive installed locally on your machine. If not, you can download the application installer here. Run the Google Drive app (or maybe it already is and just being snuggy by the notification area on your taskbar), and sign in with your Google account that has the doc collections you want to make copies of.

Local Google Drive installation

Setup and Syncing

A setup window should appear and you’ll have the option to set where you want to place your Google Drive folder locally. If you don’t have that too many documents, everything should quickly sync up right after the setup. You’ll know it’s still syncing when you see the icon being shiny (I really don’t know how to exactly describe it – my vocabulary is bad, and I should feel bad). Right-click on it and select Open Google Drive folder. You should see a folder window similar to the one shown below.

Files and subcollections in the local Google Drive folder

Copying and Re-syncing

Now select the collection you wish to backup. Do a copy-and-paste combo within the same window to create a duplicate of the selected collection.

Duplicating a Google Docs collection inside the local Google Drive folder

For a big amount of data, you might have to wait for a few minutes for the re-syncing to finish.

Local Google Drive folder starts syncing with the cloud folder

At times you might encounter an error notification as shown below telling you that some files can’t be synced. I think this happens when the collection you’re trying to duplicate contains documents that you don’t own (just shared with you). I can’t be sure enough but you won’t be experiencing this if you’re copying files that you own yourself.

Error encountered during the syncing process

Local Google Drive folder successfully synced with the cloud folder

When re-syncing’s done, you can then rename your new collection here or just do it in the cloud app. You may go check the new copy of your Google Docs collection in the cloud.

Copied Google Docs collection now accessible in the cloud or web app version

Hurray! Hope this helped you as well. Hey, you might know people who’s been scratching their heads about the same problem too. That’s another way of me saying it would be great if you’d share this with others. Haha. :)

Related Links

If you liked this post, you can subscribe to my feed. I’m also on Twitter and if you feel like asking something. I also secretly want to be your friend.

For more great content like this go to: Bibiano Wenceslao

]]>
http://bibianowenceslao.com/how-to-create-copies-of-a-google-docs-collection-or-folder/feed/ 12
Google Analytics Data on Geckoboard Custom Widgets via Google Docs Spreadsheet http://bibianowenceslao.com/google-analytics-geckoboard-custom-widgets-google-docs-spreadsheet/ http://bibianowenceslao.com/google-analytics-geckoboard-custom-widgets-google-docs-spreadsheet/#comments Tue, 15 May 2012 16:01:09 +0000 http://bibianowenceslao.com/?p=65 This content was originally found at: Google Analytics Data on Geckoboard Custom Widgets via Google Docs Spreadsheet

Learn how to fetch Google Analytics data via Data Feed API in a Google Docs spreadsheet and then create a feed URL to use on a Geckoboard custom widget.

For more great content like this go to: Bibiano Wenceslao

]]>
This content was originally found at: Google Analytics Data on Geckoboard Custom Widgets via Google Docs Spreadsheet

About a month ago, I came across Bill French of iPadCTO (a really, really cool person by the way) who wrote on the Geckoboard Blog, April last year, about how Google Docs can be used to provide real-time data for Geckoboard custom widgets. With further modifications, the approach really proved its use in monitoring site metrics at a glance through Geckoboard dashboards though back then we had to manually update or “hardcode” the metric values from Google Analytics daily.

Enter Jamie Steven of SEOmoz who wrote on their blog, October last year, about using GA Data Feed API to fetch data from Google Analytics to Google Docs. To quote him: “Realtime Google Analytics data inside a Google Doc—a panacea!” Indeed. AutomateAnalytics.com’s Mikael Thuneberg was the person behind the nifty app script that drives the whole fetching process.

With the two methods now at hand, I thought to myself: hey, how about a combination of both? Aha! Automation.

The Process

I’ll intently skip on the details of the process flow. Everything should be self-explanatory anyways if you’ve gone through the two awesome aforementioned blog posts which I highly recommend that you do before or after reading everything here. Simply put, here’s how things work:

Through the Data Feed API, Google Analytics data are…

Google Analytics Traffic Data

…fetched right inside a Google Docs spreadsheet using a particular app script that runs in the background, and another one that creates a feed URL based on the pulled data that is then used…

Using Google Analytics Data Feed API at Google Docs

…to present specific data sets in custom widgets on a Geckoboard dashboard. Ta daaaaa! Real-time data!

Google Analytics Data on Geckoboard Dashboard

The Tool

Google Analytics Data on Geckoboard Custom Widgets via Google Docs Spreadsheet
(Click to open the spreadsheet in a new tab/window)

Everything’s pretty much understandable once you see the document. Make sure to make a copy of it before proceeding. I used the same Settings sheet Jamie created because it already looks great. Functionality-wise, it has everything I needed. He even made a screencast at the source blog post on how to connect Google Analytics to Google Docs through this sheet. Just make sure you got all the required information correct. As Jamie points out on the 6th step in the Instructions section on the sheet, “Once the area below the Profile ID shows an Auth Token (a very long alphanumeric string) you are ready to access your Google Analytics data…”

Settings Sheet on Google Docs

I’ve also created three-row sets as one GeckoBoard custom widget only supports up to three metric values maximum, particularly the Rag numbers only and the Rag column and numbers widgets. I’ll just discuss further on the details of some fields and a minor portion of the app script that needs to be altered and/or populated manually. By the way, I’m sincerely not well-versed with scripts and the terms involved so I might have misused or mixed up some of them in this post e.g. macros and functions. My apologies in advance for these mistakes.

Metric and Filter

Again, reading the aforesaid posts will really get you a solid foundation in understanding how things work. You’ve probably set everything right in the Settings sheet by now. When using the  geckoData sheet, make sure first that you’ve already configured the End Date, Date Range, and most importantly the Spreadsheet Key (right after making a copy of the original public spreadsheet, you’ll have to use the generated spreadsheet key from your copy) before you proceed with anything else.

Copying the Spreadsheet Key

In my example (an old, unattended Blogger blog by the way, hence the very low traffic values you’ve probably noticed above), I’ve defined visits as a Metric, then paired it with Filters such as ga:medium==organic to name one. For more information on other Metrics and Filters, check out Google’s Dimensions & Metrics Reference page.

Configuring Dimensions, Metrics and Filters - Google Analytics API

Custom Label

This is one segment where you can freely choose how a specific metric should be labeled (see first column below) which gets paired with its corresponding value and shows up on the custom widget in the Geckoboard dashboard.

Using Basic Spreadsheet Functions on Google Docs

Supported Custom Charts & Widgets

Before moving forward, I’d just like to make some things clear. This approach has its own limitations when it comes to the types of custom Geckoboard charts and widgets it supports. There’s actually just four of them but you can always get creative with your data using native Google Docs spreadsheet functions and still be able to present data well visually even when just using the four of those.

The script that generates the XML code also relies on the Widget Type input (case-sensitive) hence it’s important to know what to put in there so you don’t end up wondering what when wrong in the end without knowing that the error started at this stage of the process.

Funnel Chart

On the spreadsheet: Input “Funnel” under Widget Type
On the Geckoboard dashboard: Add widget > Custom Charts > Funnel chart

Funnel Chart

Geck-O-Meter

On the spreadsheet: Input “GeckoMeter” under Widget Type
On the Geckoboard dashboard: Add widget > Custom Charts > Geck-O-Meter

Geck-O-Meter

RAG Column & Numbers

On the spreadsheet: Input “Rag” or “RagPercent” (if your data is in percentage) under Widget Type
On the Geckoboard dashboard: Add widget > Custom Widgets > RAG Column & Numbers

RAG Column & Numbers

RAG Numbers

On the spreadsheet: Input “Rag” or “RagPercent” (if your data is in percentage) under Widget Type
On the Geckoboard dashboard: Add widget > Custom Widgets > RAG Numbers

RAG Numbers

Update: Added new supported custom widgets. Credit goes to Patrick Linthorst for the Pie Chart widget XML-generating script.

Number Only

On the spreadsheet: Input “Number” under Widget Type
On the Geckoboard dashboard: Add widget > Custom Widgets > Number & Secondary Stat

Number Only

Pie Chart

On the spreadsheet: Input “PieChart” under Widget Type
On the Geckoboard dashboard: Add widget > Custom Charts > Pie Chart

Pulling in Values using the getGAdata() Macro Function

This is where Thuneberg’s script works its magic. After setting the Metrics (and Filters if necessary), values should automatically show up here, pulled straight from Google Analytics as shown below. You can also use basic spreadsheet functions to create a new set of values (i.e. percentage) from the ones that are already fetched as shown on the image above.

getGAdata() Function Fetches Data from Google Analytics Into Google Docs

Here’s the whole data fetching script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
function getGAauthenticationToken(email, password) {
//Fetches GA authentication token, which can then be used to fetch data with the getGAdata function
//Created by Mikael Thuneberg
    try {
        if (typeof email == "undefined") {
            return "Email address missing";
        }
        if (typeof password == "undefined") {
            return "Password missing";
        }
        if (email.length == 0) {
            return "Email address missing";
        }
        if (password.length == 0) {
            return "Password missing";
        }
        password = encodeURIComponent(password);
        var responseStr
        var response = UrlFetchApp.fetch("https://www.google.com/accounts/ClientLogin", {
            method: "post",
            payload: "accountType=GOOGLE&Email=" + email + "&Passwd=" + password + "&service=analytics&Source=Mikael Thuneberg-GA Google Docs functions-1.0"
        });
        responseStr = response.getContentText();
        responseStr = responseStr.slice(responseStr.search("Auth=") + 5, responseStr.length);
        return responseStr;
    } catch (e) {
        if (e.message.indexOf("CaptchaRequired") != -1) {
            return "Complete CAPTCHA at http://www.google.com/accounts/" + e.message.slice(e.message.indexOf("CaptchaUrl=") + 11, e.message.length);
        } else {
            return "Authentication failed (" + e.message + ")";
        }
    }
}

function getGAaccountData(authToken, dataType, includeHeaders, maxRows, startFromRow) {
//Fetches account data for the authenticated user
//Input authentication token produced by the getGAauthenticationToken function
//If dataType parameter is omitted, the functions fetches a list profiles to which the user has access
//By specifying the dataType parameters as "goals", the functions will fetch a list of goals by profile
//By specifying the dataType parameters as "segments", the functions will fetch a list of advanced segments
//Created by Mikael Thuneberg
  if (typeof authToken == "undefined") {
        return "Authentication token missing";
    }
    dataType = (typeof dataType == "undefined") ? "profiles" : dataType;
    maxRows = (typeof maxRows == "undefined") ? 200 : maxRows;
    maxRows = (typeof maxRows == "string") ? 200 : maxRows;
    startFromRow = (typeof startFromRow == "undefined") ? 1 : startFromRow;
    startFromRow = (typeof startFromRow == "string") ? 1 : startFromRow;
    if (authToken.length == 0) {
        return "Authentication token missing";
    }
    if (dataType.length == 0) {
        dataType = "profiles";
    }
    if (authToken.indexOf("Authentication failed") != -1) {
        return "Authentication failed";
    }
    try {
        authToken = authToken.replace(/\n/g, "");
        dataType = dataType.toLowerCase();
        var URL = "https://www.google.com/analytics/feeds/accounts/default?max-results=" + maxRows + "&start-index=" + startFromRow
        var responseStr;
        var response = UrlFetchApp.fetch(URL, {
            method: "get",
            headers: {
                "Authorization": "GoogleLogin auth=" + authToken,
                "GData-Version": "2"
            }
        });
        responseStr = response.getContentText();
        var XMLdoc = Xml.parse(responseStr);
        var lapset2;
        var TempArray = [];
        var RowArray = [];
        var HeaderArray = [];
        if (includeHeaders == true) {
            var rivi = 1;
            if (dataType == "segments") {
                HeaderArray[0] = "Segment ID";
                HeaderArray[1] = "Segment Name";
                HeaderArray[2] = "Segment Definition";
            } else {
                HeaderArray[0] = "Account Name";
                HeaderArray[1] = "Profile Title";
                HeaderArray[2] = "Profile Number";
            }
            TempArray[0] = HeaderArray;
        } else {
            var rivi = 0;
        }
        var sar = 0;
        var lapset;
        var dataFound = false;
        if (dataType == "segments") {
            lapset = XMLdoc.getElement().getElements();
            for (i = 0; i < lapset.length; i++) {
                if (lapset[i].getName().getLocalName() == "segment") {
                    sar = 0;
                    RowArray[0] = lapset[i].getAttribute("id").getValue();
                    RowArray[1] = lapset[i].getAttribute("name").getValue();
                    lapset2 = lapset[i].getElements();
                    for (j = 0; j < lapset2.length; j++) {
                        if (lapset2[j].getName().getLocalName() == "definition") {
                            RowArray[2] = lapset2[j].getText();
                        }
                    }
                    TempArray[rivi] = RowArray;
                    RowArray = [];
                    dataFound = true;
                    rivi++;
                    if (rivi == maxRows) {
                        return TempArray;
                    }
                } else {
                    if (lapset[i].getName().getLocalName() == "entry") {
                        break;
                    }
                }
            }
        } else { // datatype = profiles
            lapset = XMLdoc.getElement().getElements("entry");
            for (i = 0; i < lapset.length; i++) {
                sar = 0;
                lapset2 = lapset[i].getElements();
                for (j = 0; j < lapset2.length; j++) {
                    if (lapset2[j].getName().getLocalName() == "title") {
                        RowArray[1] = " " + lapset2[j].getText();
                        dataFound = true;
                    } else {
                        if (lapset2[j].getName().getLocalName() == "property") {
                            if (lapset2[j].getAttribute("name").getValue() == "ga:accountName") {
                                RowArray[0] = lapset2[j].getAttribute("value").getValue();
                            }
                            if (lapset2[j].getAttribute("name").getValue() == "ga:profileId") {
                                RowArray[2] = lapset2[j].getAttribute("value").getValue();
                                break;
                            }
                        }
                    }
                }
                TempArray[rivi] = RowArray;
                RowArray = [];
                dataFound = true;
                rivi++;
                if (rivi == maxRows) {
                    return TempArray;
                }
            }
        }
        if (dataFound == false) {
            return "No data found";
        }
        return TempArray;
    } catch (e) {
        return "Fetching account data failed (" + e.message + ")";
    }
}

function getGAdata(authToken, profileNumber, metrics, startDate, endDate, filters, dimensions, segment, sort, includeHeaders, maxRows, startFromRow) {
//Fetches data from the GA profile specified, using the authentication token generated by the getGAauthenticationToken function
//For instructions on the parameters, see http://bit.ly/bUYMDs
//Created by Mikael Thuneberg
  try {
        startDate.getYear();
    } catch (e) {
        return "Date Required";
    }
    try {
        endDate.getYear();
    } catch (e) {
        return "Invalid end date";
    }
    try {
        if (typeof authToken == "undefined") {
            return "Authentication token missing";
        }
        if (typeof profileNumber == "undefined") {
            return "Profile number missing";
        }
        if (typeof metrics == "undefined") {
            return "Specify a metric";
        }
        if (profileNumber != parseInt(profileNumber)) {
            return "Invalid profile number";
        }
        filters = (typeof filters == "undefined") ? "" : filters;
        dimensions = (typeof dimensions == "undefined") ? "" : dimensions;
        segment = (typeof segment == "undefined") ? "" : segment;
        maxRows = (typeof maxRows == "undefined") ? 100 : maxRows;
        maxRows = (typeof maxRows == "string") ? 100 : maxRows;
        startFromRow = (typeof startFromRow == "undefined") ? 1 : startFromRow;
        startFromRow = (typeof startFromRow == "string") ? 1 : startFromRow;
        if (authToken.length == 0) {
            return "Authentication token missing";
        }
        if (profileNumber.length == 0) {
            return "Profile number missing";
        }
        if (metrics.length == 0) {
            return "Specify a metric";
        }
        if (authToken.indexOf("Authentication failed") != -1) {
            return "Authentication failed";
        }
        authToken = authToken.replace(/\n/g, "");
        var startDateString
        var endDateString
        var dMonth
        var dDay
        if (startDate.getMonth() + 1 < 10) {
            dMonth = "0" + (startDate.getMonth() + 1);
        } else {
            dMonth = startDate.getMonth() + 1;
        }
        if (startDate.getDate() < 10) {
            dDay = "0" + startDate.getDate();
        } else {
            dDay = startDate.getDate();
        }
        startDateString = startDate.getYear() + "-" + dMonth + "-" + dDay
        if (endDate.getMonth() + 1 < 10) {
            dMonth = "0" + (endDate.getMonth() + 1);
        } else {
            dMonth = endDate.getMonth() + 1;
        }
        if (endDate.getDate() < 10) {
            dDay = "0" + endDate.getDate();
        } else {
            dDay = endDate.getDate();
        }
        endDateString = endDate.getYear() + "-" + dMonth + "-" + dDay
        if (startDateString > endDateString) {
            return "Start date should be before end date";
        }
        var URL = "https://www.google.com/analytics/feeds/data?ids=ga:" + profileNumber + "&start-date=" + startDateString + "&end-date=" + endDateString + "&max-results=" + maxRows + "&start-index=" + startFromRow;
        if (metrics.slice(0, 3) != "ga:") {
            metrics = "ga:" + metrics;
        }
        metrics = metrics.replace(/&/g, "&ga:");
        metrics = metrics.replace(/ga:ga:/g, "ga:");
        metrics = metrics.replace(/&/g, "%2C");
        URL = URL + "&metrics=" + metrics
        if (dimensions.length > 0) {
            if (dimensions.slice(0, 3) != "ga:") {
                dimensions = "ga:" + dimensions;
            }
            dimensions = dimensions.replace(/&/g, "&ga:");
            dimensions = dimensions.replace(/ga:ga:/g, "ga:");
            dimensions = dimensions.replace(/&/g, "%2C");
            URL = URL + "&dimensions=" + dimensions;
        }
        if (filters.length > 0) {
            if (filters.slice(0, 3) != "ga:") {
                filters = "ga:" + filters;
            }
            filters = filters.replace(/,/g, ",ga:");
            filters = filters.replace(/;/g, ";ga:");
            filters = filters.replace(/ga:ga:/g, "ga:");
            filters = encodeURIComponent(filters);
            URL = URL + "&filters=" + filters;
        }
        if (typeof(segment) == "number") {
            segment = "gaid::" + segment;
        }
        if (segment.length > 0) {
            if (segment.indexOf("gaid::") == -1 && segment.indexOf("dynamic::") == -1) {
                if (segment.slice(0, 3) != "ga:") {
                    segment = "ga:" + segment;
                }
                segment = "dynamic::" + segment;
            }
            segment = encodeURIComponent(segment);
            URL = URL + "&segment=" + segment;
        }
        if (sort == true) {
            URL = URL + "&sort=-" + metrics;
        }
    }
    catch (e) {
        return "Fetching data failed (" + e.message + ")";
    }
    try {
        var randnumber = Math.random()*5000;
            Utilities.sleep(randnumber);
            Utilities.sleep(randnumber);
            Utilities.sleep(randnumber);
        var response = UrlFetchApp.fetch(URL, {
            method: "get",
            headers: {
                "Authorization": "GoogleLogin auth=" + authToken,
                "GData-Version": "2"
            }
        });
    } catch (e) {
        if (e.message.indexOf("Timeout") != -1) {
            response = UrlFetchApp.fetch(URL, {
                method: "get",
                headers: {
                    "Authorization": "GoogleLogin auth=" + authToken,
                    "GData-Version": "2"
                }
            });
        } else {
            return "Fetching data failed (" + e.message + ")";
        }
    }
    try {
        var responseStr = response.getContentText();
        var XMLdoc = Xml.parse(responseStr);
        var lapset = XMLdoc.getElement().getElements("entry");
        var lapset2;
        var TempArray = [];
        var RowArray = [];
        var HeaderArray = [];
        if (includeHeaders == true) {
            var rivi = 1;
        } else {
            var rivi = 0;
        }
        var sar = 0;
        var dataFound = false;
        for (i = 0; i < lapset.length; i++) {
            sar = 0;
            lapset2 = lapset[i].getElements();
            for (j = 0; j < lapset2.length; j++) {
                if (lapset2[j].getName().getLocalName() == "dimension") {
                    RowArray[sar] = lapset2[j].getAttribute("value").getValue();
                    if (rivi == 1) {
                        HeaderArray[sar] = lapset2[j].getAttribute("name").getValue();
                    }
                    sar++;
                }
                if (lapset2[j].getName().getLocalName() == "metric") {
                    RowArray[sar] = Number(lapset2[j].getAttribute("value").getValue());
                    if (rivi == 1) {
                        HeaderArray[sar] = lapset2[j].getAttribute("name").getValue();
                    }
                    sar++;
                }
            }
            TempArray[rivi] = RowArray;
            RowArray = [];
            dataFound = true;
            rivi++;
        }
        if (dataFound == false) {
            return 0; //changed from "No data found" to 0 (zero);
        }
        if (includeHeaders == true) {
            TempArray[0] = HeaderArray;
        }
        return TempArray;
    } catch (e) {
        return "Fetching data failed (" + e.message + ")";
    }
}

Now that the script is set, click on Select function on the toolbar and choose geckoDocs.

Selecting the Macro to Run - Script Editor Page

Hit the Run button and wait for the macro function to finish running.

Running the geckoDocs() Macro Function

After the successful run and before you leave the the Script Editor page, I just want you to take note that when you go to Resources on the menu and and select Current script’s triggers, you’ll see that I’ve already set both macro functions to run at a set time. Feel free to modify them as you see fit.

Current Script's Triggers Settings in Google Docs

Now going back to the spreadsheet, you’ll see that the XML and Feed URL sections have been populated already.

Generated XML Scripts and Feed URLs on the Spreadsheet

Below is Bill French’s script that generates the widget-specific XML codes (I’ve added the Number Widget part):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// ***
// *** Copyright (c) 2011 - Bill French, iPadCTO, ALL RIGHTS RESERVED
// ***

// ***
// Configure the spreadsheet ID
// ***
var spreadsheetID = SpreadsheetApp.getActiveSpreadsheet().getId();

function geckoDocs()
{
  var sheet = SpreadsheetApp.openById(spreadsheetID).getSheetByName("geckoData");

  // process all the widgets
  for (var i = 12; i <= 17; i++) // var i = [firstDataRow]; i <= [lastDataRow]
  {
    var _i = i.toString();
    var geckoType = "E" + _i;
    var dataRange = "F" + _i;
    var xmlTarget = "G" + _i;
    sheet.getRange(xmlTarget).setValue(genXML(sheet.getRange(geckoType).getValues(),
      sheet.getRange(dataRange).getValues(), sheet));
  }

}

function genXML(_type, _range, _sheet)
{

    // setup the sheet objects
    var _range = _range.toString();

    // test for multi-column non-continguous ranges
    if(_range.indexOf(",") > 0)
    {

      // parse the rages and grab the values
      _range1 = _range.substr(0, _range.indexOf(","));
      _range2 = _range.substr(_range.indexOf(",") + 1);

      // grab the range values
      var range1 = _sheet.getRange(_range1);
      range1 = range1.getValues();
      var range2 = _sheet.getRange(_range2);
      range2 = range2.getValues();

      // merge the non-contiguous range values into one value set

      // establish value array
      var values = new Array();
      for (i = 0; i < 3; ++ i)
    values [i] = new Array();

      // assemble data
      for (var i = 0; i < range1.length; i++)
      {
          values[0 + i][0] = range1[0 + i][0];
          values[0 + i][1] = range2[0 + i][0];
      }

    } else {
      var range = _sheet.getRange(_range);
      var values = range.getValues();
    }

    // establish the XML header
    var xml = "<?xml version='1.0' encoding='UTF-8'?>";
   
    // Rag Widget
    if(_type == "Rag")
    {
      xml = xml + " \
        <root>"
;
            for (var i = 0; i < values.length; i++)
            {
                xml = xml + "<item>"
                xml = xml + "<value>" + values[0 + i][1] + "</value>";
                xml = xml + "<text>" + values[0 + i][0] + "</text>";
                xml = xml + "</item>"
            }
        xml = xml + "</root>";
    }
 
    // RagPercent Widget
    if(_type == "RagPercent")
    {
      xml = xml + " \
        <root>"
;
            for (var i = 0; i < values.length; i++)
            {
                xml = xml + "<item>"
                xml = xml + "<value>" + values[0 + i][1] + "</value>";
                xml = xml + "<text>" + values[0 + i][0] + "</text>";
                xml = xml + "<prefix><![CDATA[%]]></prefix>"
                xml = xml + "</item>"
            }
        xml = xml + "</root>";
    }
   
    // Funnel
    if(_type == "Funnel")
    {
      xml = xml + " \
        <root> \
            <type>standard</type> \
            <percentage>hide</percentage>"

            for (var i = 0; i < values.length; i++)
            {
                xml = xml + "<item>"
                xml = xml + "<value>" + values[0 + i][1] + "</value>";
                xml = xml + "<label><![CDATA[" + values[0 + i][0] + "]]></label>";
                xml = xml + "</item>"
            }
        xml = xml + "</root>";
    }

    // GeckoMeter Widget
    if(_type == "GeckoMeter")
    {
      xml = xml + " \
        <root> \
          <item>"
+ values + "</item> \
          <type>reverse</type> \
          <min> \
            <value>10</value> \
            <text>Min Score</text> \
          </min> \
          <max> \
            <value>100</value> \
            <text>Max Score</text> \
          </max> \
        </root>"
;
    }
 
  // Number Widget --- Added by Bibiano Wenceslao
    if(_type == "Number")
    {
      xml = xml + " \
        <root> \
          <item> \
             <value>"
+ values + "</value> \
             <text></text> \
          </item> \
        </root>"
;
    }
 
  // Pie Chart --- Contributed by Patrick Linthorst
  if(_type == "PieChart")
  {
    xml = xml + " \
    <root> \
    "

    for (var i = 0; i < values.length; i++)
    {
        xml = xml + "<item>"
        xml = xml + "<value>" + values[0 + i][1] + "</value>";
        xml = xml + "<label></label>";
        xml = xml + "</item>"
    }
    xml = xml + "</root>";
  }

    return xml;
}

Just to make sure that the sheet has already been published (or has to be published), on the menu go to File > Publish to the Web and hit the Start Publishing or Republish now button.

Publishing a Google Docs Spreadsheet to the Web

All you’ll have to do now is to use each Feed URL on the proper custom widget or chart that you need to create…

Using the XML Feed URL on a Geckoboard Custom Widget

…and you’ll have yourself a custom Geckoboard Dashboard with data pulled straight from Google Analytics!

Google Analytics Data on Geckoboard Custom Widgets and Charts

Related Links

If you’re encountering any issues regarding the process, you may leave a comment below. Or maybe you just want to drop by and say “hi”. Either way, I’d be happy to respond. (Hurray! My first post!) :D

If you liked this post, you can subscribe to my feed. I’m also on Twitter and if you feel like asking something. I also secretly want to be your friend.

For more great content like this go to: Bibiano Wenceslao

]]>
http://bibianowenceslao.com/google-analytics-geckoboard-custom-widgets-google-docs-spreadsheet/feed/ 30