Created
July 11, 2020 13:41
-
-
Save adamsilverstein/89b788447882a13b3f1222f9b950978c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Created by Adam Silverstein, based on original work by Rick Viscomi (@rick_viscomi) | |
// Adapted from https://ithoughthecamewithyou.com/post/automate-google-pagespeed-insights-with-apps-script by Robert Ellison | |
// @copyright 2020 Google LLC | |
var scriptProperties = PropertiesService.getScriptProperties(); | |
var pageSpeedApiKey = scriptProperties.getProperty('PSI_API_KEY'); | |
function queueAllDomains() { { | |
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); | |
var sheet = spreadsheet.getSheetByName('Alerts'); | |
var domainSheet = spreadsheet.getSheetByName('Domains'); | |
sheet.clear(); | |
sheet = spreadsheet.getSheetByName('Queue'); | |
sheet.clear(); | |
for ( var i = 1; i <= domainSheet.getLastRow(); i++ ) { | |
var url = domainSheet.getRange( i, 1 ).getValue(); | |
var subURLs = getSubURLs( url ); | |
var allURLs = [ url, ...subURLs ]; | |
for ( var x = 0; x < allURLs.length; x++ ) { | |
var subURL = allURLs[ x ]; | |
sheet.appendRow( [ allURLs[ x ] ] ); | |
} | |
} | |
sheet.appendRow( [""] ); | |
} | |
// Email any alerts. | |
/* if ( alerts.length > 0 ) { | |
emailAlerts( alerts ); | |
}*/ | |
} | |
function runNextQueuedItem() { | |
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); | |
var sheet = spreadsheet.getSheetByName('Queue'); | |
try { | |
var subURL = sheet.getRange( 1, 1 ).getValue(); | |
sheet.deleteRow( 1 ); | |
if ( subURL == '' ) { | |
return; | |
} | |
var desktop = callPageSpeed(subURL, 'desktop'); | |
var mobile = callPageSpeed(subURL, 'mobile'); | |
addRow( subURL, desktop, mobile ); | |
} catch (e) { | |
console.log( "error for " + subURL, e ); | |
}; | |
try { | |
checkAMP( subURL ); | |
var alert = checkForAlerts( subURL ); | |
if ( alert && alert != '' ) { | |
sheet = spreadsheet.getSheetByName('Alerts'); | |
sheet.appendRow( [ Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd'), subURL, alert ] ); | |
} | |
} catch (e) { | |
console.log( "error for " + subURL, e ); | |
}; | |
} | |
// @param { string } URL The url to identify. | |
function isPrimary( URL ) { | |
var slashes = URL.length - ( URL.replace( /\//g, '' ) ).length; | |
return slashes == 3; | |
} | |
// Get sub-urls for a site - pull the latest/first post, page, category and author from the WordPress REST API. | |
function getSubURLs( url ) { | |
var subUrls = []; | |
var endpoints = [ 'posts?per_page=1', 'posts?per_page=1&offset=75', 'pages?per_page=1', 'categories?orderby=count&order=desc&per_page=1', 'users?per_page=1' ]; | |
for (var i = 0; i < endpoints.length; i++ ) { | |
try { | |
var endpoint = url + 'wp-json/wp/v2/' + endpoints[ i ]; | |
var response = UrlFetchApp.fetch( endpoint ); | |
var json = response.getContentText(); | |
var data = JSON.parse( json ); | |
if ( data[0] && data[0].link ) { | |
subUrls.push( data[0].link ); | |
} | |
} catch (e) { | |
console.log( "error for " + endpoint, e ); | |
}; | |
} | |
return subUrls; | |
} | |
// Send an email about any alerts. | |
function emailAlerts( alerts ) { | |
var message = "The following issues were detected with Newspack sites: \n\n"; | |
for (var i = 0; i < alerts.length; i++) { | |
message = message + alerts[ i ] + "\n\n"; | |
} | |
MailApp.sendEmail( | |
'adamjs@google.com,jefferson.rabb@automattic.com,albertomedina@google.com,thierrymuller@google.com,ivan.uravic@automattic.com', | |
'Newspack site performance monitoring alert.', | |
message | |
); | |
} | |
function callPageSpeed(url, strategy) { | |
var pageSpeedUrl = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=' + url + '&strategy=' + strategy + '&key=' + pageSpeedApiKey; | |
var response = UrlFetchApp.fetch(pageSpeedUrl); | |
var json = response.getContentText(); | |
return JSON.parse(json); | |
} | |
function addRow(url, desktop, mobile) { | |
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); | |
var sheet = spreadsheet.getSheetByName('Lighthouse Score'); | |
try{ | |
sheet.appendRow([ | |
Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd'), | |
url, | |
getScore(desktop), | |
getScore(mobile), | |
'', isPrimary( url ) ? "PRIMARY" : "SECONDARY" | |
]); | |
} catch (e) { | |
console.log( "error with getScore for " + url, e ); | |
} | |
sheet = spreadsheet.getSheetByName('FCP'); | |
try{ | |
sheet.appendRow([ | |
Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd'), | |
url, | |
getFCP(desktop), | |
getFCP(mobile), | |
'', isPrimary( url ) ? "PRIMARY" : "SECONDARY" | |
]); | |
} catch (e) { | |
console.log( "error with getFCP for " + url, e ); | |
} | |
sheet = spreadsheet.getSheetByName('FID'); | |
try{ | |
sheet.appendRow([ | |
Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd'), | |
url, | |
getFID(desktop), | |
getFID(mobile), | |
'', isPrimary( url ) ? "PRIMARY" : "SECONDARY" | |
]); | |
} catch (e) { | |
console.log( "error with getFID for " + url, e ); | |
} | |
sheet = spreadsheet.getSheetByName('LCP'); | |
try{ | |
sheet.appendRow([ | |
Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd'), | |
url, | |
getLCP(desktop), | |
getLCP(mobile), | |
'', isPrimary( url ) ? "PRIMARY" : "SECONDARY" | |
]); | |
} catch (e) { | |
console.log( "error with getLCP for " + url, e ); | |
} | |
sheet = spreadsheet.getSheetByName('CLS'); | |
try{ | |
sheet.appendRow([ | |
Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd'), | |
url, | |
getCLS(desktop), | |
getCLS(mobile), | |
'', isPrimary( url ) ? "PRIMARY" : "SECONDARY" | |
]); | |
} catch (e) { | |
console.log( "error with getCLS for " + url, e ); | |
} | |
} | |
// Extract the lighthouse score from a data set. | |
function getScore( data ) { | |
return data.lighthouseResult.categories.performance.score * 100; | |
} | |
// Extract the First Contentful Paint data. | |
function getFCP( data ) { | |
return data.loadingExperience.metrics.FIRST_CONTENTFUL_PAINT_MS.distributions[0].proportion; | |
} | |
// Extract the First Input Delay data. | |
function getFID( data ) { | |
return data.loadingExperience.metrics.FIRST_INPUT_DELAY_MS.distributions[0].proportion; | |
} | |
// Extract the largest contentful paint data. | |
function getLCP( data ) { | |
return data.loadingExperience.metrics.LARGEST_CONTENTFUL_PAINT_MS.distributions[0].proportion; | |
} | |
// Extract the Cumulative Layout Shift data. | |
function getCLS( data ) { | |
return data.loadingExperience.metrics.CUMULATIVE_LAYOUT_SHIFT_SCORE.distributions[0].proportion; | |
} | |
// Check the AMP status of a URL be reviewing `<html>` tag attributes. | |
function checkAMP( u ) { | |
var htmlContent = UrlFetchApp.fetch( u ).getContentText(); | |
var regex = new RegExp( /<html([^>]*?)>/ ); | |
var tagContent = regex.exec( htmlContent)[0]; //htmlContent.search( /<html([^>]*?)>/ ); | |
var ampIsValid = tagContent.indexOf( "amp" ) > -1; // amp attribute means that no invalid markup is being kept, and the AMP plugin is intending to serve the page as valid AMP. | |
var ssrActive = tagContent.indexOf( "transformed" ) > -1; // transformed attribute means SSR is being done. | |
var ssrComplete = tagContent.indexOf( "i-amphtml-no-boilerplate" ) > -1; // i-amphtml-no-boilerplate attribute means SSR was able to be fully applied. | |
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); | |
var sheet = spreadsheet.getSheetByName( "AMP Status" ); | |
try { | |
sheet.appendRow([ | |
Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd'), | |
u, | |
ampIsValid, | |
ssrActive, | |
ssrComplete, | |
tagContent, | |
'', isPrimary( u ) ? "PRIMARY" : "SECONDARY" | |
]); | |
} catch (e) { | |
console.log( "error with checkAMP for " + u ); | |
} | |
} | |
// Check a URL for alert conditions. | |
function checkForAlerts( url ) { | |
// Check each sheet, looking for changes above a threshold percent. | |
var speedchecks = [ | |
'Lighthouse Score', | |
'FCP', | |
'FID' | |
]; | |
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); | |
for ( var i = 0; i< speedchecks.length; i++ ) { | |
var sheet = spreadsheet.getSheetByName( speedchecks[ i ] ) | |
var found = sheet.createTextFinder( url ).findAll(); | |
if ( found.length < 2 ) { | |
continue; | |
} | |
var lastRow = found[ found.length - 1 ].getRow(); | |
var previousRow = found[ found.length - 2 ].getRow(); | |
// Desktop column C, Mobile column D | |
sheet.getRange('C' + lastRow).activate(); | |
var lastDesktop = sheet.getSelection().getCurrentCell().getValue(); | |
sheet.getRange('D' + lastRow).activate(); | |
var lastMobile = sheet.getSelection().getCurrentCell().getValue(); | |
sheet.getRange('C' + previousRow).activate(); | |
var previousDesktop = sheet.getSelection().getCurrentCell().getValue(); | |
sheet.getRange('D' + previousRow).activate(); | |
var previousMobile = sheet.getSelection().getCurrentCell().getValue(); | |
var desktopChange = previousDesktop - lastDesktop; | |
var mobileChange = previousMobile - lastMobile; | |
var desktopPercentDecrease = roundToTwoDigits( desktopChange / previousDesktop * 100 ); | |
var mobilePercentDecrease = roundToTwoDigits( mobileChange / previousMobile * 100 ); | |
var result = ''; | |
if ( desktopPercentDecrease > 15 ) { | |
result = result + 'Desktop score decreased by ' + desktopPercentDecrease + ' percent (Yesterday it was ' + roundToTwoDigits( previousDesktop ) + ' and today it is ' + roundToTwoDigits( lastDesktop ) + '). '; | |
} | |
if ( mobilePercentDecrease > 15 ) { | |
result = result + 'Mobile score decreased by ' + mobilePercentDecrease + ' percent (Yesterday it was ' + roundToTwoDigits( previousMobile ) + ' and today it is ' + roundToTwoDigits( lastMobile ) + '). '; | |
} | |
if ( result != '' ) { | |
return 'Issue detected for ' + url + " " + speedchecks[ i ] + ". " + result; | |
} | |
return ''; | |
} | |
} | |
function roundToTwoDigits( num ) { | |
return Math.round( num * 100 ) / 100; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment