sitespeed.io/lib/plugins/slack/index.js

232 lines
5.8 KiB
JavaScript

import { promisify } from 'node:util';
import intel from 'intel';
import Slack from 'node-slack';
import merge from 'lodash.merge';
import set from 'lodash.set';
import { SitespeedioPlugin } from '@sitespeed.io/plugin';
import { DataCollector } from './dataCollector.js';
import { getAttachements } from './attachements.js';
import { getSummary } from './summary.js';
import { throwIfMissing } from '../../support/util.js';
const log = intel.getLogger('sitespeedio.plugin.slack');
const defaultConfig = {
userName: 'Sitespeed.io',
type: 'all',
limitWarning: 90,
limitError: 80,
limitMetric: 'coachScore'
};
function send(options, dataCollector, context, screenshotType, alias) {
const slackOptions = merge({}, defaultConfig, options.slack);
const slack = new Slack(slackOptions.hookUrl);
const send = promisify(slack.send.bind(slack));
const type = slackOptions.type;
const pageErrors = [];
let logo = 'https://www.sitespeed.io/img/slack/sitespeed-logo-slack.png';
let channel = slackOptions.channel;
if (channel && !channel.startsWith('#')) {
channel = `#${channel}`;
}
for (const url of dataCollector.getURLs()) {
const errors = dataCollector.getURLData(url).errors;
if (errors) {
pageErrors.push(errors);
}
}
let text = '';
if (['summary', 'all', 'error'].includes(type)) {
const sum = getSummary(
dataCollector,
pageErrors,
context.resultUrls,
context.name,
options
);
text += sum.summaryText + '\n' + sum.errorText;
logo = sum.logo;
}
let attachments = [];
if (['url', 'all', 'error'].includes(type)) {
attachments = getAttachements(
dataCollector,
context.resultUrls,
slackOptions,
screenshotType,
alias
);
}
if ((type === 'error' && pageErrors.length > 0) || type !== 'error') {
log.debug(
'Sending message to Slack channel %s and username %s',
slackOptions.channel,
slackOptions.userName
);
return send({
text,
icon_url: logo,
channel,
mrkdwn: true,
username: slackOptions.userName,
attachments
}).catch(error => {
if (error.errno === 'ETIMEDOUT') {
log.warn('Timeout sending Slack message.');
} else {
throw error;
}
});
}
}
/**
* Detect configurations for a static website provider and return its name.
*
* @param {object} options
* @return {string?} The provider name (if any could be detected)
*/
function staticPagesProvider(options) {
const s3Options = options.s3;
try {
throwIfMissing(s3Options, ['bucketname'], 's3');
if (s3Options.key || s3Options.secret) {
throwIfMissing(s3Options, ['key', 'secret'], 's3');
}
return 's3';
} catch {
log.debug('s3 is not configured');
}
const gcsOptions = options.gcs;
try {
throwIfMissing(gcsOptions, ['projectId', 'key', 'bucketname'], 'gcs');
return 'gcs';
} catch {
log.debug('gcs is not configured');
}
return;
}
export default class SlackPlugin extends SitespeedioPlugin {
constructor(options, context, queue) {
super({ name: 'slack', options, context, queue });
}
open(context, options = {}) {
const slackOptions = options.slack || {};
throwIfMissing(slackOptions, ['hookUrl', 'userName'], 'slack');
this.dataCollector = new DataCollector(context);
this.context = context;
this.options = options;
this.screenshotType;
this.alias = {};
}
processMessage(message) {
const dataCollector = this.dataCollector;
switch (message.type) {
case 'browsertime.alias': {
this.alias[message.url] = message.data;
break;
}
case 'error': {
if (message.url) {
dataCollector.addErrorForUrl(
message.url,
message.source,
message.data,
this.alias[message.url]
);
} else {
dataCollector.addError(message.source, message.data);
}
break;
}
case 'browsertime.run':
case 'browsertime.pageSummary':
case 'pagexray.run':
case 'pagexray.pageSummary':
case 'coach.run':
case 'coach.pageSummary': {
dataCollector.addDataForUrl(
message.url,
message.type,
message.data,
message.runIndex,
this.alias[message.url]
);
break;
}
case 'coach.summary':
case 'pagexray.summary':
case 'browsertime.summary': {
const data = {};
set(data, message.type, message.data);
dataCollector.addSummary(data);
break;
}
// collect screenshot type
case 'browsertime.config': {
if (message.data.screenshot === true) {
this.screenshotType = message.data.screenshotType;
}
break;
}
case 'remove.url': {
dataCollector.removeUrl(message.url);
break;
}
case 'html.finished': {
const provider = staticPagesProvider(this.options);
if (provider) {
log.debug(
'A static website hosting provider (%s) is configured. ' +
'The Slack message should be sent after the HTML report is uploaded',
provider
);
} else {
// Send the notification right away if no static website provider was configured
return send(
this.options,
dataCollector,
this.context,
this.screenshotType,
this.alias
);
}
break;
}
case 'gcs.finished':
case 'ftp.finished':
case 'scp.finished':
case 's3.finished': {
return send(
this.options,
dataCollector,
this.context,
this.screenshotType,
this.alias
);
}
}
}
}
export const config = defaultConfig;