Bibiano Wenceslao http://bibianowenceslao.com WordPress, Inbound Marketing, Spreadsheets, Work Hacks and Life Sun, 28 Apr 2013 02:15:07 +0000 en-US hourly 1 http://wordpress.org/?v=3.5.1 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 Bibiano Wenceslao http://bibianowenceslao.com/?p=429

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 [...]

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

For more great content like this go to: Bibiano Wenceslao

]]>
http://bibianowenceslao.com/must-have-google-chrome-extensions-for-productivity/feed/ 2
How to Create Copies of a Google Docs Collection 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 Bibiano Wenceslao http://bibianowenceslao.com/?p=239

This content was originally found at: How to Create Copies of a Google Docs Collection

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 [...]

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

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.

Options available for a Google Docs collection

No copy functionality for collections? Okay. :(

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.

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

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/ 10
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 Bibiano Wenceslao http://bibianowenceslao.com/?p=65

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 [...]

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

For more great content like this go to: Bibiano Wenceslao

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