Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query both api datacenters #7

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 102 additions & 39 deletions frontend/src/ResultsRow.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,100 @@
import {h, Component} from 'preact'

function renderSuccess(server = '') {
return (
<div className='d-inline-flex align-items-center mr-2'>
<span className='text-success oi oi-circle-check mr-2'/> {server.toUpperCase()}
</div>
)
}

function renderError(server = '', text, details) {
return (
<div className='d-inline-flex align-items-center has-tooltip mr-2'>
<span className='text-danger oi oi-warning mr-2'/> {server.toUpperCase()}
{text && (<div className='ml-2'>{text}</div>)}
<div className='status-tooltip'>{details}</div>
</div>
)
}

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, (<pre>{firstServer.schemaChanges}</pre>))
}

return servers.map(
([server, {schemaValid, schemaChanges}]) => (
schemaValid ? renderSuccess(server) : renderError(server, null, (<pre>{schemaChanges}</pre>))
)
)
}

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, (<pre>{firstServer.snapshotChanges}</pre>))
}

return servers.map(
([server, {snapshotValid, snapshotChanges}]) => (
snapshotValid ? renderSuccess(server) : renderError(server, null, (<pre>{snapshotChanges}</pre>))
)
)
}

class ResultsRow extends Component {
render () {
const {data, className, onClick} = this.props
Expand All @@ -10,61 +105,29 @@ class ResultsRow extends Component {
<a href={data.url} target='_blank' rel='noopener noreferrer'>{data.name}</a>
</td>
<td>
{data.status < 400 && (
<span className='text-success oi oi-circle-check'/>
)}

{data.status >= 400 && (
<div className='d-flex align-items-center has-tooltip'>
<span className='text-danger oi oi-circle-x' />
<div className='ml-2'>{data.status}</div>
<div className='status-tooltip'>{data.error || 'Unknown error'}</div>
</div>
)}
{renderStatus(data)}
</td>
<td>
{data.schemaValid === undefined && '—'}

{data.schemaValid !== undefined && data.schemaValid && (
<span className='text-success oi oi-circle-check'/>
)}

{data.schemaValid !== undefined && !data.schemaValid && (
<div className='has-tooltip'>
<span className='text-danger oi oi-warning'/>
<div className='status-tooltip'><pre>{data.schemaChanges}</pre></div>
</div>
)}
{renderSchema(data)}
</td>
<td>
{data.snapshotValid === undefined && '—'}

{data.snapshotValid !== undefined && data.snapshotValid && (
<span className='text-success oi oi-circle-check'/>
)}

{data.snapshotValid !== undefined && !data.snapshotValid && (
<div className='has-tooltip'>
<span className='text-danger oi oi-warning'/>
<div className='status-tooltip'><pre>{data.snapshotChanges}</pre></div>
</div>
)}
{renderSnapshot(data)}
</td>
<td>
<div className='d-flex align-items-center'>
{data.duration <= 1500 && (
{data.servers.eu.duration <= 1500 && (
<span className='text-success oi oi-circle-check'/>
)}

{data.duration > 1500 && data.duration <= 3000 && (
{data.servers.eu.duration > 1500 && data.servers.eu.duration <= 3000 && (
<span className='text-warning oi oi-timer'/>
)}

{data.duration > 3000 && (
{data.servers.eu.duration > 3000 && (
<span className='text-danger oi oi-timer'/>
)}

<div className='ml-2'>{data.duration !== 0 ? `${data.duration.toLocaleString()} ms` : ''}</div>
<div className='ml-2'>{data.servers.eu.duration !== 0 ? `${data.servers.eu.duration.toLocaleString()} ms` : ''}</div>
</div>
</td>
</tr>
Expand Down
25 changes: 11 additions & 14 deletions frontend/src/ResultsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<ResultsRow
data={data}
onClick={() => this.setState({expanded: !this.state.expanded})}
className='result-row--summary'
/>
<tr onClick={() => this.setState({expanded: !this.state.expanded})} className="result-row--summary">
<td width={400}>{text}</td>
<td><span className='text-success oi oi-circle-check'/></td>
<td><span className='text-success oi oi-circle-check'/></td>
<td><span className='text-success oi oi-circle-check'/></td>
<td><div className='d-flex align-items-center'>
<span className='text-success oi oi-circle-check'/>
<div className='ml-2'>{averageDuration.toLocaleString()}ms</div>
</div></td>
</tr>
)
}
}
Expand Down
36 changes: 22 additions & 14 deletions frontend/src/sideEffects.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
37 changes: 36 additions & 1 deletion worker/src/requestApi.js
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
11 changes: 9 additions & 2 deletions worker/src/runTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)`)
Expand Down
27 changes: 10 additions & 17 deletions worker/src/testEndpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -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