diff --git a/frontend/src/ResultsRow.js b/frontend/src/ResultsRow.js index b14116f..969cc34 100644 --- a/frontend/src/ResultsRow.js +++ b/frontend/src/ResultsRow.js @@ -1,5 +1,100 @@ import {h, Component} from 'preact' +function renderSuccess(server = '') { + return ( +
+ {server.toUpperCase()} +
+ ) +} + +function renderError(server = '', text, details) { + return ( +
+ {server.toUpperCase()} + {text && (
{text}
)} +
{details}
+
+ ) +} + +function renderStatus(endpoint) { + const servers = Object.entries(endpoint.servers) + const broken = servers.filter(([,{status}]) => status >= 400) + + if(broken.length === 0) { + return renderSuccess(); + } + + return servers.map( + ([server, {status, error}]) => ( + status < 400 ? renderSuccess(server) : renderError(server, status, error || 'Unknown error') + ) + ) +} + +function renderSchema(endpoint) { + const servers = Object.entries(endpoint.servers) + + if(servers.every(([, {schemaValid}]) => schemaValid === undefined)) { + return ('—') + } + + const broken = servers.filter(([,{schemaValid}]) => !schemaValid) + + if(broken.length === 0) { + return renderSuccess() + } + + const firstServer = servers[0][1]; + const sameError = servers.every( + ([, {schemaValid, schemaChanges}]) => + schemaValid === firstServer.schemaValid && + schemaChanges === firstServer.schemaChanges + ) + + if(sameError) { + return renderError('', null, (
{firstServer.schemaChanges}
)) + } + + return servers.map( + ([server, {schemaValid, schemaChanges}]) => ( + schemaValid ? renderSuccess(server) : renderError(server, null, (
{schemaChanges}
)) + ) + ) +} + +function renderSnapshot(endpoint) { + const servers = Object.entries(endpoint.servers) + + if(servers.every(([, {snapshotValid}]) => snapshotValid === undefined)) { + return ('—') + } + + const broken = servers.filter(([,{snapshotValid}]) => !snapshotValid) + + if(broken.length === 0) { + return renderSuccess(); + } + + const firstServer = servers[0][1]; + const sameError = servers.every( + ([, {snapshotValid, snapshotChanges}]) => + snapshotValid === firstServer.snapshotValid && + snapshotChanges === firstServer.snapshotChanges + ) + + if(sameError) { + return renderError('', null, (
{firstServer.snapshotChanges}
)) + } + + return servers.map( + ([server, {snapshotValid, snapshotChanges}]) => ( + snapshotValid ? renderSuccess(server) : renderError(server, null, (
{snapshotChanges}
)) + ) + ) +} + class ResultsRow extends Component { render () { const {data, className, onClick} = this.props @@ -10,61 +105,29 @@ class ResultsRow extends Component { {data.name} - {data.status < 400 && ( - - )} - - {data.status >= 400 && ( -
- -
{data.status}
-
{data.error || 'Unknown error'}
-
- )} + {renderStatus(data)} - {data.schemaValid === undefined && '—'} - - {data.schemaValid !== undefined && data.schemaValid && ( - - )} - - {data.schemaValid !== undefined && !data.schemaValid && ( -
- -
{data.schemaChanges}
-
- )} + {renderSchema(data)} - {data.snapshotValid === undefined && '—'} - - {data.snapshotValid !== undefined && data.snapshotValid && ( - - )} - - {data.snapshotValid !== undefined && !data.snapshotValid && ( -
- -
{data.snapshotChanges}
-
- )} + {renderSnapshot(data)}
- {data.duration <= 1500 && ( + {data.servers.eu.duration <= 1500 && ( )} - {data.duration > 1500 && data.duration <= 3000 && ( + {data.servers.eu.duration > 1500 && data.servers.eu.duration <= 3000 && ( )} - {data.duration > 3000 && ( + {data.servers.eu.duration > 3000 && ( )} -
{data.duration !== 0 ? `${data.duration.toLocaleString()} ms` : ''}
+
{data.servers.eu.duration !== 0 ? `${data.servers.eu.duration.toLocaleString()} ms` : ''}
diff --git a/frontend/src/ResultsTable.js b/frontend/src/ResultsTable.js index 52c00eb..edb9e7e 100644 --- a/frontend/src/ResultsTable.js +++ b/frontend/src/ResultsTable.js @@ -52,22 +52,19 @@ class ResultsTable extends Component { const count = workingCount === results.data.length ? 'All' : workingCount const text = `${prefix} ${count} endpoints fully operational 🎉` - const averageDuration = Math.round(average(workingResults.map(x => x.duration))) - - const data = { - name: text, - status: 200, - schemaValid: true, - snapshotValid: true, - duration: averageDuration - } + const averageDuration = Math.round(average(workingResults.map(x => x.servers.eu.duration))) return ( - this.setState({expanded: !this.state.expanded})} - className='result-row--summary' - /> + this.setState({expanded: !this.state.expanded})} className="result-row--summary"> + {text} + + + +
+ +
{averageDuration.toLocaleString()}ms
+
+ ) } } diff --git a/frontend/src/sideEffects.js b/frontend/src/sideEffects.js index 3dbbf0f..88a1ec0 100644 --- a/frontend/src/sideEffects.js +++ b/frontend/src/sideEffects.js @@ -9,7 +9,7 @@ export async function getLatestTest () { return endpoint }) - result.data.sort((a, b) => b.duration - a.duration) + result.data.sort((a, b) => b.servers.eu.duration - a.servers.eu.duration) result.data.sort((a, b) => b.severity - a.severity) result.updated_at = new Date(result.updated_at) @@ -18,21 +18,29 @@ export async function getLatestTest () { } function calculateSeverity (endpoint) { - if (endpoint.status >= 400) { - return 3 + const severity = Object.values(endpoint.servers).map( + (server) => { + if (server.status >= 400) { + return 3 + } + + if (server.schemaValid === false || server.snapshotValid === false) { + return 2 + } + + return 0 + } + ).reduce( + (total, severity) => total + severity, 0 + ) + + if (endpoint.servers.eu.duration > 3000) { + return severity + 1 } - if (endpoint.schemaValid === false || endpoint.snapshotValid === false) { - return 2 + if (endpoint.servers.eu.duration > 1500) { + return severity + 0.5 } - if (endpoint.duration > 3000) { - return 1 - } - - if (endpoint.duration > 1500) { - return 0.5 - } - - return 0 + return severity } diff --git a/worker/src/requestApi.js b/worker/src/requestApi.js index af86440..0a64e39 100644 --- a/worker/src/requestApi.js +++ b/worker/src/requestApi.js @@ -1,11 +1,46 @@ const fetch = require('node-fetch') +const { Agent } = require('https'); + +const server = { + us: createApiAgent('34.226.105.80'), + eu: createApiAgent('52.58.154.210') +}; + +function createApiAgent (ip) { + // create new https agent + const agent = new Agent() + + // save original createConnection function + const createConnection = agent.createConnection + + // override createConnection + agent.createConnection = function(opts, callback) { + // set custom dns lookup resolving to either us or eu datacenter + opts.lookup = (_1, _2, cb) => cb(null, ip, 4) + + // call original create connection call + return createConnection.call(agent, opts, callback) + } + + return agent +} async function requestApi (url) { + return Promise.all(Object.entries(servers).map( + ([server, agent]) => [server, requestApiInternal(url, agent)] + )).then( + (servers) => servers.reduce( + (servers, [server, result]) => ({...servers, [server]: result}), {} + ) + ) +} + +async function requestApiInternal (url, agent) { const start = new Date().getTime() let response = null try { - response = await fetch(url) + response = await fetch(url, { agent }) } catch (err) {} const end = new Date().getTime() diff --git a/worker/src/runTests.js b/worker/src/runTests.js index 9b4049c..2e345eb 100644 --- a/worker/src/runTests.js +++ b/worker/src/runTests.js @@ -12,8 +12,15 @@ async function runTests () { results.push(await testEndpoint(endpoints[i])) } - let possiblyBroken = results.filter(x => x.schemaValid === false || x.snapshotValid === false) - let definitelyBroken = results.filter(x => x.status !== 200) + let possiblyBroken = results.filter( + (endpoint) => Object.values(endpoint.servers).some( + (server) => server.schemaValid === false || server.snapshotValid === false + ) + ) + + let definitelyBroken = results.filter( + (endpoint) => Object.values(endpoint.servers).some((server) => server.status !== 200) + ) if (definitelyBroken.length > 0) { console.log(`❗ ERROR: ${definitelyBroken.length} API endpoint(s)`) diff --git a/worker/src/testEndpoint.js b/worker/src/testEndpoint.js index f47afbe..216bfea 100644 --- a/worker/src/testEndpoint.js +++ b/worker/src/testEndpoint.js @@ -5,27 +5,20 @@ const matchSnapshot = require('./matchSnapshot') async function testEndpoint (endpoint) { const url = generateUrl(endpoint.url) - const {duration, response} = await requestApi(url) + const requests = await requestApi(url) - const error = response.status >= 400 && response.content && response.content.text - - let result = { + return { url, name: endpoint.name, - status: response.status, - duration, - error - } - - if (result.status !== 200) { - return result + servers: Object.entries(requests) + .reduce((a, [server, {response: {status, content}, duration }]) => ({ + ...a, [server]: { + status, duration, + ...(endpoint.matchSchema ? matchSchema(endpoint, content) : {}), + ...(endpoint.matchSnapshot ? matchSnapshot(endpoint, content) : {}) + } + }), []) } - - return Object.assign( - result, - endpoint.matchSchema ? matchSchema(endpoint, response.content) : {}, - endpoint.matchSnapshot ? matchSnapshot(endpoint, response.content) : {} - ) } module.exports = testEndpoint