// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
http://www.apache.org/licenses/LICENSE-2.0//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Mobile Performance from PageSpeed Insights - Single Account
*
* Produces a report showing how well landing pages are set up for mobile
* devices and highlights potential areas for improvement. See :
*
https://developers.google.com/google-ads/scripts/docs/solutions/mobile-pagespeed * for more details.
*
* @author Google Ads Scripts Team [
adwords...@googlegroups.com]
*
* @version 1.3.3
*
* @changelog
* - version 1.3.3
* - Added guidance for desktop analysis.
* - version 1.3.2
* - Bugfix to improve table sorting comparator.
* - version 1.3.1
* - Bugfix for handling the absence of optional values in PageSpeed response.
* - version 1.3
* - Removed the need for the user to take a copy of the spreadsheet.
* - Added the ability to customize the Campaign and Ad Group limits.
* - version 1.2.1
* - Improvements to time zone handling.
* - version 1.2
* - Bug fix for handling empty results from URLs.
* - Error reporting in spreadsheet for failed URL fetches.
* - version 1.1
* - Updated the comments to be in sync with the guide.
* - version 1.0
* - Released initial version.
*/
// See "Obtaining an API key" at
//
https://developers.google.com/google-ads/scripts/docs/solutions/mobile-pagespeedvar API_KEY = 'my API key';
var EMAIL_RECIPIENTS = ['my email'];
// If you wish to add extra URLs to checked, list them here as a
// comma-separated list eg. ['
http://abc.xyz', '
http://www.google.com']
var EXTRA_URLS_TO_CHECK = [];
// By default, the script returns analysis of how the site performs on mobile.
// Change the following from 'mobile' to 'desktop' to perform desktop analysis.
var PLATFORM_TYPE = 'mobile';
/**
* The URL of the template spreadsheet for each report created.
*/
var SPREADSHEET_TEMPLATE =
'my spreadsheet url';
var PAGESPEED_URL =
'
https://www.googleapis.com/pagespeedonline/v2/runPagespeed?';
/*
* The maximum number of Campaigns to sample within the account.
*/
var CAMPAIGN_LIMIT = 50000;
/*
* The maximum number of Ad Groups to sample within the account.
*/
var ADGROUP_LIMIT = 50000;
/**
* These are the sampling limits for how many of each element will be examined
* in each AdGroup.
*/
var KEYWORD_LIMIT = 20;
var SITELINK_LIMIT = 20;
var AD_LIMIT = 30;
/**
* Specifies the amount of time in seconds required to do the URL fetching and
* result generation. As this is the last step, entities in the account will be
* iterated over until this point.
*/
var URL_FETCH_TIME_SECS = 8 * 60;
/**
* Specifies the amount of time in seconds required to write to and format the
* spreadsheet.
*/
var SPREADSHEET_PREP_TIME_SECS = 4 * 60;
/**
* Represents the number of retries to use with the PageSpeed service.
*/
var MAX_RETRIES = 3;
/**
* The main entry point for execution.
*/
function main() {
if (!defaultsChanged()) {
Logger.log('Please change the default configuration values and retry');
return;
}
var accountName = AdsApp.currentAccount().getName();
var urlStore = getUrlsFromAccount();
var result = getPageSpeedResultsForUrls(urlStore);
var spreadsheet = createPageSpeedSpreadsheet(accountName +
': PageSpeed Insights - Mobile Analysis', result);
spreadsheet.addEditors(EMAIL_RECIPIENTS);
sendEmail(spreadsheet.getUrl());
}
/**
* Sends an email to the user with the results of the run.
*
* @param {string} url URL of the spreadsheet.
*/
function sendEmail(url) {
var footerStyle = 'color: #aaaaaa; font-style: italic;';
var scriptsLink = '
https://developers.google.com/google-ads/scripts/';
var subject = 'Google Ads PageSpeed URL-Sampling Script Results - ' +
getDateStringInTimeZone('dd MMM yyyy');
var htmlBody = '<html><body>' +
'<p>Hello,</p>' +
'<p>A Google Ads Script has run successfully and the output is ' +
'available here:' +
'<ul><li><a href="' + url +
'">Google Ads PageSpeed URL-Sampling Script Results</a></li></ul></p>' +
'<p>Regards,</p>' +
'<span style="' + footerStyle + '">This email was automatically ' +
'generated by <a href="' + scriptsLink + '">Google Ads Scripts</a>.' +
'</span></body></html>';
var body = 'Please enable HTML to view this report.';
var options = {htmlBody: htmlBody};
MailApp.sendEmail(EMAIL_RECIPIENTS, subject, body, options);
}
/**
* Checks to see that placeholder defaults have been changed.
*
* @return {boolean} true if placeholders have been changed, false otherwise.
*/
function defaultsChanged() {
if (API_KEY == 'INSERT_PAGESPEED_API_KEY_HERE' ||
SPREADSHEET_TEMPLATE == 'INSERT_SPREADSHEET_URL_HERE' ||
JSON.stringify(EMAIL_RECIPIENTS) ==
JSON.stringify(['INSERT_EMAIL_ADDRESS_HERE'])) {
return false;
}
return true;
}
/**
* Creates a new PageSpeed spreadsheet and populates it with result data.
*
* @param {string} name The name to give to the spreadsheet.
* @param {Object} pageSpeedResult The result from PageSpeed, and the number of
* URLs that could have been chosen from.
* @return {Spreadsheet} The newly-created spreadsheet.
*/
function createPageSpeedSpreadsheet(name, pageSpeedResult) {
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_TEMPLATE).copy(name);
spreadsheet.setSpreadsheetTimeZone(AdsApp.currentAccount().getTimeZone());
var data = pageSpeedResult.table;
var activeSheet = spreadsheet.getActiveSheet();
var rowStub = spreadsheet.getRangeByName('ResultRowStub');
var top = rowStub.getRow();
var left = rowStub.getColumn();
var cols = rowStub.getNumColumns();
if (data.length > 2) { // No need to extend the template if num rows <= 2
activeSheet.insertRowsAfter(
spreadsheet.getRangeByName('EmptyUrlRow').getRow(), data.length);
rowStub.copyTo(activeSheet.getRange(top + 1, left, data.length - 2, cols));
}
// Extend the formulas and headings to accommodate the data.
if (data.length && data[0].length > 4) {
var metricsRange = activeSheet
.getRange(top - 6, left + cols, data.length + 5, data[0].length - 4);
activeSheet.getRange(top - 6, left + cols - 1, data.length + 5)
.copyTo(metricsRange);
// Paste in the data values.
activeSheet.getRange(top - 1, left, data.length, data[0].length)
.setValues(data);
// Move the 'Powered by Google Ads Scripts' to right corner of table.
spreadsheet.getRangeByName('PoweredByText').moveTo(activeSheet.getRange(1,
data[0].length + 1, 1, 1));
// Set summary - date and number of URLs chosen from.
var summaryDate = getDateStringInTimeZone('dd MMM yyyy');
spreadsheet.getRangeByName('SummaryDate').setValue('Summary as of ' +
summaryDate + '. Results drawn from ' + pageSpeedResult.totalUrls +
' URLs.');
}
// Add errors if they exist
if (pageSpeedResult.errors.length) {
var nextRow = spreadsheet.getRangeByName('FirstErrorRow').getRow();
var errorRange = activeSheet.getRange(nextRow, 2,
pageSpeedResult.errors.length, 2);
errorRange.setValues(pageSpeedResult.errors);
}
return spreadsheet;
}
/**
* This function takes a collection of URLs as provided by the UrlStore object
* and gets results from the PageSpeed service. However, two important things :
* (1) It only processes a handful, as determined by URL_LIMIT.
* (2) The URLs returned from iterating on the UrlStore are in a specific
* order, designed to produce as much variety as possible (removing the need
* to process all the URLs in an account.
*
* @param {UrlStore} urlStore Object containing URLs to process.
* @return {Object} An object with three properties: 'table' - the 2d table of
* results, 'totalUrls' - the number of URLs chosen from, and errors.
*/
function getPageSpeedResultsForUrls(urlStore) {
var count = 0;
// Associative array for column headings and contextual help URLs.
var headings = {};
var errors = {};
// Record results on a per-URL basis.
var pageSpeedResults = {};
var urlTotalCount = 0;
for (var url in urlStore) {
if (hasRemainingTimeForUrlFetches()) {
var result = getPageSpeedResultForSingleUrl(url);
if (!result.error) {
pageSpeedResults[url] = result.pageSpeedInfo;
var columnsResult = result.columnsInfo;
// Loop through each heading element; the PageSpeed Insights API
// doesn't always return URLs for each column heading, so aggregate
// these across each call to get the most complete list.
var columnHeadings = Object.keys(columnsResult);
for (var i = 0, lenI = columnHeadings.length; i < lenI; i++) {
var columnHeading = columnHeadings[i];
if (!headings[columnHeading] || (headings[columnHeading] &&
headings[columnHeading].length <
columnsResult[columnHeading].length)) {
headings[columnHeading] = columnsResult[columnHeading];
}
}
} else {
errors[url] = result.error;
}
count++;
}
urlTotalCount++;
}
var tableHeadings = ['URL', 'Speed', 'Usability'];
var headingKeys = Object.keys(headings);
for (var y = 0, lenY = headingKeys.length; y < lenY; y++) {
tableHeadings.push(headingKeys[y]);
}
var table = [];
var pageSpeedResultsUrls = Object.keys(pageSpeedResults);
for (var r = 0, lenR = pageSpeedResultsUrls.length; r < lenR; r++) {
var resultUrl = pageSpeedResultsUrls[r];
var row = [toPageSpeedHyperlinkFormula(resultUrl)];
var data = pageSpeedResults[resultUrl];
for (var j = 1, lenJ = tableHeadings.length; j < lenJ; j++) {
row.push(data[tableHeadings[j]]);
}
table.push(row);
}
// Present the table back in the order worst-performing-first.
table.sort(function(first, second) {
var f1 = isNaN(parseInt(first[1])) ? 0 : parseInt(first[1]);
var f2 = isNaN(parseInt(first[2])) ? 0 : parseInt(first[2]);
var s1 = isNaN(parseInt(second[1])) ? 0 : parseInt(second[1]);
var s2 = isNaN(parseInt(second[2])) ? 0 : parseInt(second[2]);
if (f1 + f2 < s1 + s2) {
return -1;
} else if (f1 + f2 > s1 + s2) {
return 1;
}
return 0;
});
// Add hyperlinks to all column headings where they are available.
for (var h = 0, lenH = tableHeadings.length; h < lenH; h++) {
// Sheets cannot have multiple links in a single cell at the moment :-/
if (headings[tableHeadings[h]] &&
typeof(headings[tableHeadings[h]]) === 'object') {
tableHeadings[h] = '=HYPERLINK("' + headings[tableHeadings[h]][0] +
'","' + tableHeadings[h] + '")';
}
}
// Form table from errors
var errorTable = [];
var errorKeys = Object.keys(errors);
for (var k = 0; k < errorKeys.length; k++) {
errorTable.push([errorKeys[k], errors[errorKeys[k]]]);
}
table.unshift(tableHeadings);
return {
table: table,
totalUrls: urlTotalCount,
errors: errorTable
};
}
/**
* Given a URL, returns a spreadsheet formula that displays the URL yet links to
* the PageSpeed URL for examining this.
*
* @param {string} url The URL to embed in the Hyperlink formula.
* @return {string} A string representation of the spreadsheet formula.
*/
function toPageSpeedHyperlinkFormula(url) {
return '=HYPERLINK("' +
'
https://developers.google.com/speed/pagespeed/insights/?url=' + url +
'&tab=' + PLATFORM_TYPE +'","' + url + '")';
}
/**
* Creates an object of results metrics from the parsed results of a call to
* the PageSpeed service.
*
* @param {Object} parsedPageSpeedResponse The object returned from PageSpeed.
* @return {Object} An associative array with entries for each metric.
*/
function extractResultRow(parsedPageSpeedResponse) {
var urlScores = {};
if (parsedPageSpeedResponse.ruleGroups) {
var ruleGroups = parsedPageSpeedResponse.ruleGroups;
// At least one of the SPEED or USABILITY properties will exist, but not
// necessarily both.
urlScores.Speed = ruleGroups.SPEED ? ruleGroups.SPEED.score : '-';
urlScores.Usability = ruleGroups.USABILITY ?
ruleGroups.USABILITY.score : '-';
}
if (parsedPageSpeedResponse.formattedResults &&
parsedPageSpeedResponse.formattedResults.ruleResults) {
var resultParts = parsedPageSpeedResponse.formattedResults.ruleResults;
for (var partName in resultParts) {
var part = resultParts[partName];
urlScores[part.localizedRuleName] = part.ruleImpact;
}
}
return urlScores;
}
/**
* Extracts the headings for the metrics returned from PageSpeed, and any
* associated help URLs.
*
* @param {Object} parsedPageSpeedResponse The object returned from PageSpeed.
* @return {Object} An associative array used to store column-headings seen in
* the response. This can take two forms:
* (1) {'heading':'heading', ...} - this form is where no help URLs are
* known.
* (2) {'heading': [url1, ...]} - where one or more URLs is returned that
* provides help on the particular heading item.
*/
function extractColumnsInfo(parsedPageSpeedResponse) {
var columnsInfo = {};
if (parsedPageSpeedResponse.formattedResults &&
parsedPageSpeedResponse.formattedResults.ruleResults) {
var resultParts = parsedPageSpeedResponse.formattedResults.ruleResults;
for (var partName in resultParts) {
var part = resultParts[partName];
if (!columnsInfo[part.localizedRuleName]) {
columnsInfo[part.localizedRuleName] = part.localizedRuleName;
}
// Find help URLs in the response
var summary = part.summary;
if (summary && summary.args) {
var argList = summary.args;
for (var i = 0, lenI = argList.length; i < lenI; i++) {
var arg = argList[i];
if ((arg.type) && (arg.type == 'HYPERLINK') &&
(arg.key) && (arg.key == 'LINK') &&
(arg.value)) {
columnsInfo[part.localizedRuleName] = [arg.value];
}
}
}
if (part.urlBlocks) {
var blocks = part.urlBlocks;
var urls = [];
for (var j = 0, lenJ = blocks.length; j < lenJ; j++) {
var block = blocks[j];
if (block.header) {
var header = block.header;
if (header.args) {
var args = header.args;
for (var k = 0, lenK = args.length; k < lenK; k++) {
var argument = args[k];
if ((argument.type) &&
(argument.type == 'HYPERLINK') &&
(argument.key) &&
(argument.key == 'LINK') &&
(argument.value)) {
urls.push(argument.value);
}
}
}
}
}
if (urls.length > 0) {
columnsInfo[part.localizedRuleName] = urls;
}
}
}
}
return columnsInfo;
}
/**
* Extracts a suitable error message to display for a failed URL. The error
* could be passed in in the nested PageSpeed error format, or there could have
* been a more fundamental error in the fetching of the URL. Extract the
* relevant message in each case.
*
* @param {string} errorMessage The error string.
* @return {string} A formatted error message.
*/
function formatErrorMessage(errorMessage) {
var formattedMessage = null;
if (!errorMessage) {
formattedMessage = 'Unknown error message';
} else {
try {
var parsedError = JSON.parse(errorMessage);
// This is the nested structure expected from PageSpeed
if (parsedError.error && parsedError.error.errors) {
var firstError = parsedError.error.errors[0];
formattedMessage = firstError.message;
} else if (parsedError.message) {
formattedMessage = parsedError.message;
} else {
formattedMessage = errorMessage.toString();
}
} catch (e) {
formattedMessage = errorMessage.toString();
}
}
return formattedMessage;
}
/**
* Calls the PageSpeed API for a single URL, and attempts to parse the resulting
* JSON. If successful, produces an object for the metrics returned, and an
* object detailing the headings and help URLs seen.
*
* @param {string} url The URL to run PageSpeed for.
* @return {Object} An object with pageSpeed metrics, column-heading info
* and error properties.
*/
function getPageSpeedResultForSingleUrl(url) {
var parsedResponse = null;
var errorMessage = null;
var retries = 0;
while ((!parsedResponse || parsedResponse.responseCode !== 200) &&
retries < MAX_RETRIES) {
errorMessage = null;
var fetchResult = checkUrl(url);
if (fetchResult.responseText) {
try {
parsedResponse = JSON.parse(fetchResult.responseText);
break;
} catch (e) {
errorMessage = formatErrorMessage(e);
}
} else {
errorMessage = formatErrorMessage(fetchResult.error);
}
retries++;
Utilities.sleep(1000 * Math.pow(2, retries));
}
if (!errorMessage) {
var columnsInfo = extractColumnsInfo(parsedResponse);
var urlScores = extractResultRow(parsedResponse);
}
return {
pageSpeedInfo: urlScores,
columnsInfo: columnsInfo,
error: errorMessage
};
}
/**
* Gets the most representative URL that would be used on a mobile device
* taking into account Upgraded URLs.
*
* @param {Entity} entity A Google Ads entity such as an Ad, Keyword or
* Sitelink.
* @return {string} The URL.
*/
function getMobileUrl(entity) {
var urls = entity.urls();
var url = null;
if (urls) {
if (urls.getMobileFinalUrl()) {
url = urls.getMobileFinalUrl();
} else if (urls.getFinalUrl()) {
url = urls.getFinalUrl();
}
}
if (!url) {
switch (entity.getEntityType()) {
case 'Ad':
case 'Keyword':
url = entity.getDestinationUrl();
break;
case 'Sitelink':
case 'AdGroupSitelink':
case 'CampaignSitelink':
url = entity.getLinkUrl();
break;
default:
Logger.log('No URL found' + entity.getEntityType());
}
}
if (url) {
url = encodeURI(decodeURIComponent(url));
}
return url;
}
/**
* Determines whether there is enough remaining time to continue iterating
* through the account.
*
* @return {boolean} Returns true if there is enough time remaining to continue
* iterating.
*/
function hasRemainingTimeForAccountIteration() {
var remainingTime = AdsApp.getExecutionInfo().getRemainingTime();
return remainingTime > SPREADSHEET_PREP_TIME_SECS + URL_FETCH_TIME_SECS;
}
/**
* Determines whether there is enough remaining time to continue fetching URLs.
*
* @return {boolean} Returns true if there is enough time remaining to continue
* fetching.
*/
function hasRemainingTimeForUrlFetches() {
var remainingTime = AdsApp.getExecutionInfo().getRemainingTime();
return remainingTime > SPREADSHEET_PREP_TIME_SECS;
}
/**
* Iterates through all the available Campaigns and AdGroups, to a limit of
* defined in CAMPAIGN_LIMIT and ADGROUP_LIMIT until the time limit is reached
* allowing enough time for the post-iteration steps, e.g. fetching and
* analysing URLs and building results.
*
* @return {UrlStore} An UrlStore object with URLs from the account.
*/
function getUrlsFromAccount() {
var urlStore = new UrlStore(EXTRA_URLS_TO_CHECK);
var campaigns = AdsApp.campaigns()
.forDateRange('LAST_30_DAYS')
.withCondition('Status = "ENABLED"')
.orderBy('Clicks DESC')
.withLimit(CAMPAIGN_LIMIT)
.get();
while (campaigns.hasNext() && hasRemainingTimeForAccountIteration()) {
var campaign = campaigns.next();
var campaignUrls = getUrlsFromCampaign(campaign);
urlStore.addUrls(campaignUrls);
}
var adGroups = AdsApp.adGroups()
.forDateRange('LAST_30_DAYS')
.withCondition('Status = "ENABLED"')
.orderBy('Clicks DESC')
.withLimit(ADGROUP_LIMIT)
.get();
while (adGroups.hasNext() && hasRemainingTimeForAccountIteration()) {
var adGroup = adGroups.next();
var adGroupUrls = getUrlsFromAdGroup(adGroup);
urlStore.addUrls(adGroupUrls);
}
return urlStore;
}
/**
* Work through an ad group's members in the account, but only up to the maximum
* specified by the SITELINK_LIMIT.
*
* @param {AdGroup} adGroup The adGroup to process.
* @return {!Array.<string>} A list of URLs.
*/
function getUrlsFromAdGroup(adGroup) {
var uniqueUrls = {};
var sitelinks =
adGroup.extensions().sitelinks().withLimit(SITELINK_LIMIT).get();
while (sitelinks.hasNext()) {
var sitelink = sitelinks.next();
var url = getMobileUrl(sitelink);
if (url) {
uniqueUrls[url] = true;
}
}
return Object.keys(uniqueUrls);
}
/**
* Work through a campaign's members in the account, but only up to the maximum
* specified by the AD_LIMIT, KEYWORD_LIMIT and SITELINK_LIMIT.
*
* @param {Campaign} campaign The campaign to process.
* @return {!Array.<string>} A list of URLs.
*/
function getUrlsFromCampaign(campaign) {
var uniqueUrls = {};
var url = null;
var sitelinks = campaign
.extensions().sitelinks().withLimit(SITELINK_LIMIT).get();
while (sitelinks.hasNext()) {
var sitelink = sitelinks.next();
url = getMobileUrl(sitelink);
if (url) {
uniqueUrls[url] = true;
}
}
var ads = campaign.ads().forDateRange('LAST_30_DAYS')
.withCondition('Status = "ENABLED"')
.orderBy('Clicks DESC')
.withLimit(AD_LIMIT)
.get();
while (ads.hasNext()) {
var ad = ads.next();
url = getMobileUrl(ad);
if (url) {
uniqueUrls[url] = true;
}
}
var keywords = campaign.keywords().forDateRange('LAST_30_DAYS')
.withCondition('Status = "ENABLED"')
.orderBy('Clicks DESC')
.withLimit(KEYWORD_LIMIT)
.get();
while (keywords.hasNext()) {
var keyword = keywords.next();
url = getMobileUrl(keyword);
if (url) {
uniqueUrls[url] = true;
}
}
return Object.keys(uniqueUrls);
}
/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {Date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account's time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}
/**
* UrlStore - this is an object that takes URLs, added one by one, and then
* allows them to be iterated through in a particular order, which aims to
* maximise the variety between the returned URLs.
*
* This works by splitting the URL into three parts: host, path and params
* In comparing two URLs, most weight is given if the hosts differ, then if the
* paths differ, and finally if the params differ.
*
* UrlStore sets up a tree with 3 levels corresponding to the above. The full
* URL exists at the leaf level. When a request is made for an iterator, a copy
* is taken, and a path through the tree is taken, using the first host. Each
* entry is removed from the tree as it is used, and the layers are rotated with
* each call such that the next call will result in a different host being used
* (where possible).
*
* Where opt_manualUrls are supplied at construction time, these will take
* precedence over URLs added subsequently to the object.
*
* @param {?Array.<string>=} opt_manualUrls An optional list of URLs to check.
* @constructor
*/
function UrlStore(opt_manualUrls) {
this.manualUrls = opt_manualUrls || [];
this.paths = {};
this.re = /^(https?:\/\/[^\/]+)([^?#]*)(.*)$/;
}
/**
* Adds a URL to the UrlStore.
*
* @param {string} url The URL to add.
*/
UrlStore.prototype.addUrl = function(url) {
if (!url || this.manualUrls.indexOf(url) > -1) {
return;
}
var matches = this.re.exec(url);
if (matches) {
var host = matches[1];
var path = matches[2];
var param = matches[3];
if (!this.paths[host]) {
this.paths[host] = {};
}
var hostObj = this.paths[host];
if (!path) {
path = '/';
}
if (!hostObj[path]) {
hostObj[path] = {};
}
var pathObj = hostObj[path];
pathObj[url] = url;
}
};
/**
* Adds multiple URLs to the UrlStore.
*
* @param {!Array.<string>} urls The URLs to add.
*/
UrlStore.prototype.addUrls = function(urls) {
for (var i = 0; i < urls.length; i++) {
this.addUrl(urls[i]);
}
};
/**
* Creates and returns an iterator that tries to iterate over all available
* URLs return them in an order to maximise the difference between them.
*
* @return {UrlStoreIterator} The new iterator object.
*/
UrlStore.prototype.__iterator__ = function() {
return new UrlStoreIterator(this.paths, this.manualUrls);
};
var UrlStoreIterator = (function() {
function UrlStoreIterator(paths, manualUrls) {
this.manualUrls = manualUrls.slice();
this.urls = objectToArray_(paths);
}
UrlStoreIterator.prototype.next = function() {
if (this.manualUrls.length) {
return this.manualUrls.shift();
}
if (this.urls.length) {
return pick_(this.urls);
} else {
throw StopIteration;
}
};
function rotate_(a) {
if (a.length < 2) {
return a;
} else {
var e = a.pop();
a.unshift(e);
}
}
function pick_(a) {
if (typeof a[0] === 'string') {
return a.shift();
} else {
var element = pick_(a[0]);
if (!a[0].length) {
a.shift();
} else {
rotate_(a);
}
return element;
}
}
function objectToArray_(obj) {
if (typeof obj !== 'object') {
return obj;
}
var a = [];
for (var k in obj) {
a.push(objectToArray_(obj[k]));
}
return a;
}
return UrlStoreIterator;
})();
/**
* Runs the PageSpeed fetch.
*
* @param {string} url
* @return {Object} An object containing either the successful response from the
* server, or an error message.
*/
function checkUrl(url) {
var result = null;
var error = null;
var fullUrl = PAGESPEED_URL + 'key=' + API_KEY + '&url=' + encodeURI(url) +
'&prettyprint=false&strategy=mobile';
var params = {muteHttpExceptions: true};
try {
var pageSpeedResponse = UrlFetchApp.fetch(fullUrl, params);
if (pageSpeedResponse.getResponseCode() === 200) {
result = pageSpeedResponse.getContentText();
} else {
error = pageSpeedResponse.getContentText();
}
} catch (e) {
error = e.message;
}
return {
responseText: result,
error: error
};
}