Skip to content

Instantly share code, notes, and snippets.

@adamsilverstein
Created July 11, 2020 13:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adamsilverstein/89b788447882a13b3f1222f9b950978c to your computer and use it in GitHub Desktop.
Save adamsilverstein/89b788447882a13b3f1222f9b950978c to your computer and use it in GitHub Desktop.
// 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