diff --git a/lib/hydro.js b/lib/hydro.js index 56fdfa569994..d45e3c70e713 100644 --- a/lib/hydro.js +++ b/lib/hydro.js @@ -3,26 +3,10 @@ import fetch from 'node-fetch' import statsd from '../lib/statsd.js' import FailBot from '../lib/failbot.js' -const SCHEMAS = { - page: 'docs.v0.PageEvent', - exit: 'docs.v0.ExitEvent', - link: 'docs.v0.LinkEvent', - search: 'docs.v0.SearchEvent', - searchResult: 'docs.v0.SearchResultEvent', - navigate: 'docs.v0.NavigateEvent', - survey: 'docs.v0.SurveyEvent', - experiment: 'docs.v0.ExperimentEvent', - redirect: 'docs.v0.RedirectEvent', - clipboard: 'docs.v0.ClipboardEvent', - print: 'docs.v0.PrintEvent', - preference: 'docs.v0.PreferenceEvent', -} - export default class Hydro { constructor({ secret, endpoint } = {}) { this.secret = secret || process.env.HYDRO_SECRET this.endpoint = endpoint || process.env.HYDRO_ENDPOINT - this.schemas = SCHEMAS } /** @@ -47,20 +31,14 @@ export default class Hydro { * @param {any} value */ async publish(schema, value) { - return this.publishMany([{ schema, value }]) - } - - /** - * Publish multiple events to Hydro - * @param {[{ schema: string, value: any }]} events - */ - async publishMany(events) { const body = JSON.stringify({ - events: events.map(({ schema, value }) => ({ - schema, - value: JSON.stringify(value), // We must double-encode the value property - cluster: 'potomac', // We only have ability to publish externally to potomac cluster - })), + events: [ + { + schema, + value: JSON.stringify(value), // We must double-encode the value property + cluster: 'potomac', // We only have ability to publish externally to potomac cluster + }, + ], }) const token = this.generatePayloadHmac(body) @@ -81,23 +59,24 @@ export default class Hydro { statsd.increment(`hydro.response_code.${res.status}`, 1, statTags) statsd.increment('hydro.response_code.all', 1, statTags) - // Track hydro exceptions in Sentry, but don't track 503s because we can't do anything about service availability - if (!res.ok && res.status !== 503) { + // Track hydro exceptions in Sentry, + // but don't track 5xx because we can't do anything about service availability + if (!res.ok && res.status < 500) { const err = new Error(`Hydro request failed: ${res.statusText}`) err.status = res.status + const failures = await res.text() + FailBot.report(err, { hydroStatus: res.status, hydroText: res.statusText, + hydroFailures: failures, }) // If the Hydro request failed as an "Unprocessable Entity", log it for diagnostics if (res.status === 422) { - const failures = await res.json() console.error( - `Hydro schema validation failed:\n - Request: ${body}\n - Failures: ${JSON.stringify( - failures - )}` + `Hydro schema validation failed:\n - Request: ${body}\n - Failures: ${failures}` ) } diff --git a/lib/schema-event.js b/lib/schema-event.js index edf98df7b016..62d98c9668e1 100644 --- a/lib/schema-event.js +++ b/lib/schema-event.js @@ -461,7 +461,7 @@ const preferenceSchema = { }, } -export default { +export const eventSchema = { oneOf: [ pageSchema, exitSchema, @@ -477,3 +477,18 @@ export default { preferenceSchema, ], } + +export const hydroNames = { + page: 'docs.v0.PageEvent', + exit: 'docs.v0.ExitEvent', + link: 'docs.v0.LinkEvent', + search: 'docs.v0.SearchEvent', + searchResult: 'docs.v0.SearchResultEvent', + navigate: 'docs.v0.NavigateEvent', + survey: 'docs.v0.SurveyEvent', + experiment: 'docs.v0.ExperimentEvent', + redirect: 'docs.v0.RedirectEvent', + clipboard: 'docs.v0.ClipboardEvent', + print: 'docs.v0.PrintEvent', + preference: 'docs.v0.PreferenceEvent', +} diff --git a/middleware/events.js b/middleware/events.js index cc93fc52dc03..11e8b9a373c5 100644 --- a/middleware/events.js +++ b/middleware/events.js @@ -2,7 +2,7 @@ import express from 'express' import { omit } from 'lodash-es' import Ajv from 'ajv' import addFormats from 'ajv-formats' -import schema from '../lib/schema-event.js' +import { eventSchema, hydroNames } from '../lib/schema-event.js' const OMIT_FIELDS = ['type'] @@ -15,14 +15,14 @@ router.post('/', async function postEvents(req, res, next) { const isDev = process.env.NODE_ENV === 'development' const fields = omit(req.body, '_csrf') - if (!ajv.validate(schema, fields)) { + if (!ajv.validate(eventSchema, fields)) { return res.status(400).json(isDev ? ajv.errorsText() : {}) } if (req.hydro.maySend()) { // intentionally don't await this async request // so that the http response afterwards is sent immediately - req.hydro.publish(req.hydro.schemas[fields.type], omit(fields, OMIT_FIELDS)).catch((e) => { + req.hydro.publish(hydroNames[fields.type], omit(fields, OMIT_FIELDS)).catch((e) => { if (isDev) console.error(e) }) } diff --git a/tests/unit/hydro.js b/tests/unit/hydro.js index 993c6538a4cc..3985154a71ef 100644 --- a/tests/unit/hydro.js +++ b/tests/unit/hydro.js @@ -36,30 +36,6 @@ describe('hydro', () => { }) }) - describe('#publishMany', () => { - it('publishes multiple events to Hydro', async () => { - await hydro.publishMany([ - { schema: 'event-name', value: { pizza: true } }, - { schema: 'other-name', value: { salad: false } }, - ]) - - expect(params).toEqual({ - events: [ - { - schema: 'event-name', - value: JSON.stringify({ pizza: true }), - cluster: 'potomac', - }, - { - schema: 'other-name', - value: JSON.stringify({ salad: false }), - cluster: 'potomac', - }, - ], - }) - }) - }) - describe('#generatePayloadHmac', () => { it('returns a SHA256 HMAC string', () => { const body = JSON.stringify({ pizza: true })