From 190f4617062bb728f47bda66ac1cd2ce2929442f Mon Sep 17 00:00:00 2001 From: slice Date: Wed, 8 Aug 2018 16:43:53 -0700 Subject: [PATCH 1/2] prettier --- priv/frontend/.prettierrc | 7 ++++ priv/frontend/src/components/App.js | 6 +-- priv/frontend/src/components/Dashboard.js | 32 ++++++++-------- .../frontend/src/components/DegradedNotice.js | 7 +++- priv/frontend/src/components/Graph.js | 38 ++++++++----------- priv/frontend/src/components/Incident.js | 33 ++++++++-------- priv/frontend/src/components/Incidents.js | 14 +++---- priv/frontend/src/components/Page.js | 8 +--- priv/frontend/src/components/Service.css | 6 +-- priv/frontend/src/components/Service.js | 31 ++++++--------- .../src/components/ServicePlaceholder.js | 2 +- priv/frontend/src/components/Stage.js | 2 +- priv/frontend/src/components/Status.js | 18 ++++----- .../components/__tests__/DegradedNotice.js | 35 +++++++++++------ .../src/components/__tests__/Service.js | 18 ++++----- priv/frontend/src/icons.js | 8 +--- priv/frontend/src/index.js | 5 +-- priv/frontend/src/util.js | 4 +- priv/frontend/src/ws/client.js | 14 +++---- 19 files changed, 142 insertions(+), 146 deletions(-) create mode 100644 priv/frontend/.prettierrc diff --git a/priv/frontend/.prettierrc b/priv/frontend/.prettierrc new file mode 100644 index 0000000..d6ec4bf --- /dev/null +++ b/priv/frontend/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "arrowParens": "always", + "proseWrap": "always" +} diff --git a/priv/frontend/src/components/App.js b/priv/frontend/src/components/App.js index a8b1799..276b80a 100644 --- a/priv/frontend/src/components/App.js +++ b/priv/frontend/src/components/App.js @@ -5,11 +5,11 @@ import { Router } from '@reach/router' import Dashboard from './Dashboard' import Incidents from './Incidents' -export default function App () { +export default function App() { return ( - - + + ) } diff --git a/priv/frontend/src/components/Dashboard.js b/priv/frontend/src/components/Dashboard.js index 5aa86d7..9aa7671 100644 --- a/priv/frontend/src/components/Dashboard.js +++ b/priv/frontend/src/components/Dashboard.js @@ -19,7 +19,7 @@ export default class Dashboard extends Component { incident: null, } - async componentDidMount () { + async componentDidMount() { try { await this.loadMetrics() await this.loadIncident() @@ -53,7 +53,7 @@ export default class Dashboard extends Component { }) } - handleStatus (name, [, status]) { + handleStatus(name, [, status]) { const { status: statuses } = this.state.metrics log('updating status on:', name) @@ -75,7 +75,7 @@ export default class Dashboard extends Component { }) } - handleLatency (name, data) { + handleLatency(name, data) { const { metrics } = this.state log('adding latency entry:', data) @@ -97,50 +97,50 @@ export default class Dashboard extends Component { }) } - async loadMetrics () { + async loadMetrics() { log('loading metrics') const resp = await strictFetch(`${DOMAIN}/api/status`) this.setState({ metrics: await resp.json() }) } - async loadIncident () { + async loadIncident() { log('loading current incident') const resp = await strictFetch(`${DOMAIN}/api/incidents/current`) this.setState({ incident: await resp.json() }) } - renderNotice (services) { + renderNotice(services) { const down = services.filter(([, { status }]) => !status) // DegradedNotice should only be shown when there is no ongoing incident, // and any services are reported as down. if (!this.state.incident && down.length > 0) { - return + return } - return + return } - renderServices (services) { + renderServices(services) { const { graph: graphs } = this.state.metrics return services.map(([name, info]) => ( - + )) } - renderPlaceholders () { + renderPlaceholders() { return ( - - - + + + ) } - renderDashboardContent () { + renderDashboardContent() { if (this.state.error) { return
{this.state.error}
} @@ -162,7 +162,7 @@ export default class Dashboard extends Component { ) } - render () { + render() { return (

elstatus

diff --git a/priv/frontend/src/components/DegradedNotice.js b/priv/frontend/src/components/DegradedNotice.js index 885ec88..7e2f8b8 100644 --- a/priv/frontend/src/components/DegradedNotice.js +++ b/priv/frontend/src/components/DegradedNotice.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import './DegradedNotice.css' -export default function DegradedNotice ({ services }) { +export default function DegradedNotice({ services }) { const keys = Object.keys(services) const serviceNames = keys.join(', ') @@ -12,7 +12,10 @@ export default function DegradedNotice ({ services }) { return (
-
{keys.length} service{plural} {indicative} unreachable
+
+ {keys.length} service + {plural} {indicative} unreachable +

elstat is having trouble contacting {serviceNames}.

diff --git a/priv/frontend/src/components/Graph.js b/priv/frontend/src/components/Graph.js index 670d140..bf214fd 100644 --- a/priv/frontend/src/components/Graph.js +++ b/priv/frontend/src/components/Graph.js @@ -24,11 +24,11 @@ export default class Graph extends Component { screenWidth: window.innerWidth, } - componentDidMount () { + componentDidMount() { window.addEventListener('resize', this.handleScreenChange) } - componentWillUnmount () { + componentWillUnmount() { window.removeEventListener('resize', this.handleScreenChange) } @@ -38,7 +38,7 @@ export default class Graph extends Component { }) } - processData () { + processData() { const { data } = this.props const objects = data.map(([timestamp, latency]) => ({ @@ -50,27 +50,23 @@ export default class Graph extends Component { return objects.sort(({ timestamp: a }, { timestamp: b }) => a - b) } - isSmallScreen () { + isSmallScreen() { return this.state.screenWidth < 500 } - render () { - const yAxis = this.isSmallScreen() - ? null - : ( - `${tick}ms`} - /> - ) + render() { + const yAxis = this.isSmallScreen() ? null : ( + `${tick}ms`} + /> + ) return (
- + ms(Date.now() - tick)} @@ -78,7 +74,7 @@ export default class Graph extends Component { /> {yAxis} - + `${value}ms`} @@ -86,11 +82,7 @@ export default class Graph extends Component { separator=": " labelFormatter={() => null} /> - + - ).reverse() // show newest first + const stageNodes = stages + .map((stage) => ) + .reverse() // show newest first return (
-

{OUTAGE_TYPES[type] || type}: {title}

+

+ {OUTAGE_TYPES[type] || type}: {title} +

{content}

Started {ago} ago {end ? `, ended ${agoEnd} ago` : null}
- {stageNodes.length - ? ( -
- {stageNodes} -
- ) - : null - } + {stageNodes.length ?
{stageNodes}
: null}
) } diff --git a/priv/frontend/src/components/Incidents.js b/priv/frontend/src/components/Incidents.js index 774c527..d45dcf8 100644 --- a/priv/frontend/src/components/Incidents.js +++ b/priv/frontend/src/components/Incidents.js @@ -11,26 +11,26 @@ export default class Incidents extends React.Component { page: 0, } - componentDidMount () { + componentDidMount() { this.fetchIncidents() } - async fetchIncidents () { + async fetchIncidents() { const resp = await strictFetch(`${DOMAIN}/api/incidents/${this.state.page}`) this.setState({ incidents: await resp.json() }) } - renderIncidents () { + renderIncidents() { if (!this.state.incidents) { return null } - return this.state.incidents.map( - (incident) => - ) + return this.state.incidents.map((incident) => ( + + )) } - render () { + render() { return (

Incidents

diff --git a/priv/frontend/src/components/Page.js b/priv/frontend/src/components/Page.js index 17c8192..549b378 100644 --- a/priv/frontend/src/components/Page.js +++ b/priv/frontend/src/components/Page.js @@ -3,12 +3,8 @@ import PropTypes from 'prop-types' import './Page.css' -export default function Page ({ children }) { - return ( -
- {children} -
- ) +export default function Page({ children }) { + return
{children}
} Page.propTypes = { diff --git a/priv/frontend/src/components/Service.css b/priv/frontend/src/components/Service.css index 8e33eaf..ebe727c 100644 --- a/priv/frontend/src/components/Service.css +++ b/priv/frontend/src/components/Service.css @@ -32,13 +32,13 @@ } .service.service-alive .emoji { - color: #2ECC40; + color: #2ecc40; } .service.service-slow .emoji { - color: #FF851B; + color: #ff851b; } .service.service-dead .emoji { - color: #FF4136; + color: #ff4136; } diff --git a/priv/frontend/src/components/Service.js b/priv/frontend/src/components/Service.js index a6602a8..f93cb25 100644 --- a/priv/frontend/src/components/Service.js +++ b/priv/frontend/src/components/Service.js @@ -8,9 +8,7 @@ import './Service.css' import Graph from './Graph.js' import config from '../config.json' -const { - slow_threshold: SLOW_THRESHOLD = 1500, -} = config +const { slow_threshold: SLOW_THRESHOLD = 1500 } = config const icons = { alive: 'check-circle', @@ -24,7 +22,7 @@ const titles = { dead: 'This service is unresponsive.', } -export function getServiceState (status, latency, threshold = SLOW_THRESHOLD) { +export function getServiceState(status, latency, threshold = SLOW_THRESHOLD) { if (status && latency > threshold) { return 'slow' } @@ -32,35 +30,32 @@ export function getServiceState (status, latency, threshold = SLOW_THRESHOLD) { return status ? 'alive' : 'dead' } -export default function Service ({ graph, name, status, latency, description }) { +export default function Service({ graph, name, status, latency, description }) { const state = getServiceState(status, latency) const title = titles[state] const icon = icons[state] - const className = classnames( - 'service', - `service-${state}` - ) + const className = classnames('service', `service-${state}`) return (
- +

- {name} {latency ? ( + {name}{' '} + {latency ? ( - {Math.round(latency)}ms + {Math.round(latency)} + ms ) : null}

-

- {description} -

- {graph ? : null} +

{description}

+ {graph ? : null}
) } @@ -71,9 +66,7 @@ Service.defaultProps = { } Service.propTypes = { - graph: PropTypes.arrayOf( - PropTypes.arrayOf(PropTypes.number), - ), + graph: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)), name: PropTypes.string.isRequired, status: PropTypes.bool.isRequired, latency: PropTypes.number, diff --git a/priv/frontend/src/components/ServicePlaceholder.js b/priv/frontend/src/components/ServicePlaceholder.js index 236090f..9063802 100644 --- a/priv/frontend/src/components/ServicePlaceholder.js +++ b/priv/frontend/src/components/ServicePlaceholder.js @@ -5,7 +5,7 @@ import 'react-placeholder/lib/reactPlaceholder.css' import './ServicePlaceholder.css' -export default function ServicePlaceholder () { +export default function ServicePlaceholder() { return (
diff --git a/priv/frontend/src/components/Stage.js b/priv/frontend/src/components/Stage.js index fe3a258..dfa7330 100644 --- a/priv/frontend/src/components/Stage.js +++ b/priv/frontend/src/components/Stage.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import ms from 'ms' -export default function Stage ({ stage }) { +export default function Stage({ stage }) { const { created_at: createdAt, title, content } = stage const ago = ms(Date.now() - createdAt * 1000) diff --git a/priv/frontend/src/components/Status.js b/priv/frontend/src/components/Status.js index 5b2d0f2..5da60b0 100644 --- a/priv/frontend/src/components/Status.js +++ b/priv/frontend/src/components/Status.js @@ -5,21 +5,19 @@ import classnames from 'classnames' import './Status.css' import Incident from './Incident' -export default function Status ({ incident }) { +export default function Status({ incident }) { const incidentOngoing = incident && incident.ongoing === 1 - const view = incidentOngoing - ? - : 'All systems operational' + const view = incidentOngoing ? ( + + ) : ( + 'All systems operational' + ) const className = classnames( 'status', - incidentOngoing ? 'status-bad' : 'status-good', - ) - return ( -
- {view} -
+ incidentOngoing ? 'status-bad' : 'status-good' ) + return
{view}
} Status.propTypes = { diff --git a/priv/frontend/src/components/__tests__/DegradedNotice.js b/priv/frontend/src/components/__tests__/DegradedNotice.js index bde4912..7fb3fdd 100644 --- a/priv/frontend/src/components/__tests__/DegradedNotice.js +++ b/priv/frontend/src/components/__tests__/DegradedNotice.js @@ -4,23 +4,34 @@ import { shallow } from 'enzyme' import DegradedNotice from '../DegradedNotice' it('renders without crashing', () => { - shallow() + shallow() }) it('shows one service', () => { - const comp = shallow() + const comp = shallow() expect(comp.contains(
1 service is unreachable
)).toEqual(true) - expect(comp.contains( -

elstat is having trouble contacting first.

- )).toEqual(true) + expect( + comp.contains( +

+ elstat is having trouble contacting first. +

+ ) + ).toEqual(true) }) it('shows multiple services', () => { - const comp = shallow() - expect(comp.contains( -
3 services are unreachable
- )).toEqual(true) - expect(comp.contains( -

elstat is having trouble contacting first, second, third.

- )).toEqual(true) + const comp = shallow( + + ) + expect(comp.contains(
3 services are unreachable
)).toEqual( + true + ) + expect( + comp.contains( +

+ elstat is having trouble contacting{' '} + first, second, third. +

+ ) + ).toEqual(true) }) diff --git a/priv/frontend/src/components/__tests__/Service.js b/priv/frontend/src/components/__tests__/Service.js index 95bc760..a1e3a0b 100644 --- a/priv/frontend/src/components/__tests__/Service.js +++ b/priv/frontend/src/components/__tests__/Service.js @@ -3,11 +3,7 @@ import { shallow } from 'enzyme' import Service, { getServiceState } from '../Service' -const graph = [ - [1000, 50], - [2000, 30], - [3000, 60], -] +const graph = [[1000, 50], [2000, 30], [3000, 60]] const props = { name: 'sample service', @@ -17,19 +13,23 @@ const props = { describe('', () => { it('renders without crashing', () => { - shallow() + shallow() }) it('omits information', () => { - const comp = shallow() + const comp = shallow( + + ) expect(comp.find('h2.title').text()).toEqual('sample service ') }) it('renders proper information', () => { - const comp = shallow() + const comp = shallow() expect(comp.prop('className')).toEqual('service service-alive') expect(comp.find('h2.title').text()).toEqual('sample service 51ms') - expect(comp.contains(

a cool service

)).toEqual(true) + expect( + comp.contains(

a cool service

) + ).toEqual(true) expect(comp.contains(51ms)).toEqual(true) }) }) diff --git a/priv/frontend/src/icons.js b/priv/frontend/src/icons.js index 7ca46f3..ee01b01 100644 --- a/priv/frontend/src/icons.js +++ b/priv/frontend/src/icons.js @@ -5,10 +5,6 @@ import { faExclamationCircle, } from '@fortawesome/free-solid-svg-icons' -export default function register () { - library.add( - faCheckCircle, - faTimesCircle, - faExclamationCircle, - ) +export default function register() { + library.add(faCheckCircle, faTimesCircle, faExclamationCircle) } diff --git a/priv/frontend/src/index.js b/priv/frontend/src/index.js index aa4d2ca..3787e4a 100644 --- a/priv/frontend/src/index.js +++ b/priv/frontend/src/index.js @@ -7,7 +7,4 @@ import register from './icons.js' register() -ReactDOM.render( - , - document.getElementById('root') -) +ReactDOM.render(, document.getElementById('root')) diff --git a/priv/frontend/src/util.js b/priv/frontend/src/util.js index af6e6f6..c1774f8 100644 --- a/priv/frontend/src/util.js +++ b/priv/frontend/src/util.js @@ -8,14 +8,14 @@ export const logger = ({ name, color }) => (...args) => export const log = logger({ name: 'elstat', color: 'purple' }) -export function objectFromEntries (entries) { +export function objectFromEntries(entries) { return entries.reduce( (object, [key, value]) => ({ ...object, [key]: value }), {} ) } -export async function strictFetch (...args) { +export async function strictFetch(...args) { const resp = await fetch(...args) if (!resp.ok) { diff --git a/priv/frontend/src/ws/client.js b/priv/frontend/src/ws/client.js index 65330d1..4d1078d 100644 --- a/priv/frontend/src/ws/client.js +++ b/priv/frontend/src/ws/client.js @@ -5,7 +5,7 @@ import { logger } from '../util' const log = logger({ name: 'ws', color: 'green' }) export default class StreamingClient extends NanoEvents { - constructor (url, metrics) { + constructor(url, metrics) { super() this.url = url @@ -14,11 +14,11 @@ export default class StreamingClient extends NanoEvents { this.delay = 1000 } - send (packet) { + send(packet) { this.ws.send(JSON.stringify(packet)) } - subscribe () { + subscribe() { if (!this.metrics) { log('not subscribing to channels -- no initial metrics') return @@ -36,7 +36,7 @@ export default class StreamingClient extends NanoEvents { this.send({ op: OP.SUBSCRIBE, channels }) } - handle (packet) { + handle(packet) { const { op, c: channel, d: data } = packet if ([OP.SUBSCRIBED, OP.UNSUBSCRIBED].includes(op)) { @@ -52,11 +52,11 @@ export default class StreamingClient extends NanoEvents { } } - connect () { + connect() { const begin = Date.now() log('connecting') - const ws = this.ws = new WebSocket(this.url) + const ws = (this.ws = new WebSocket(this.url)) window.ws = ws ws.onopen = () => { @@ -68,7 +68,7 @@ export default class StreamingClient extends NanoEvents { ws.onclose = ({ code, reason }) => { log( `ws closed with code ${code} (reason: ${reason || ''}); ` + - `attempting to reconnect in ${this.delay}ms` + `attempting to reconnect in ${this.delay}ms` ) setTimeout(() => this.connect(), this.delay) From 6aae34927b5252f6425ac7d4ed09124a0f3e8c38 Mon Sep 17 00:00:00 2001 From: slice Date: Wed, 8 Aug 2018 17:06:35 -0700 Subject: [PATCH 2/2] add uptime display --- priv/frontend/src/components/Dashboard.js | 10 ++++++++-- priv/frontend/src/components/Service.css | 4 ++-- priv/frontend/src/components/Service.js | 16 +++++++++++++--- .../frontend/src/components/__tests__/Service.js | 9 +++++++-- priv/frontend/src/util.js | 5 +++++ 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/priv/frontend/src/components/Dashboard.js b/priv/frontend/src/components/Dashboard.js index 9aa7671..abd0433 100644 --- a/priv/frontend/src/components/Dashboard.js +++ b/priv/frontend/src/components/Dashboard.js @@ -124,9 +124,15 @@ export default class Dashboard extends Component { } renderServices(services) { - const { graph: graphs } = this.state.metrics + const { graph: graphs, uptime: uptimes } = this.state.metrics return services.map(([name, info]) => ( - + )) } diff --git a/priv/frontend/src/components/Service.css b/priv/frontend/src/components/Service.css index ebe727c..f31190d 100644 --- a/priv/frontend/src/components/Service.css +++ b/priv/frontend/src/components/Service.css @@ -11,13 +11,13 @@ margin: 0; } -.service .latency { +.service .information { margin-left: 0.5em; color: hsl(0, 0%, 45%); } @media (max-width: 500px) { - .service .latency { + .service .information { font-size: 1rem; } } diff --git a/priv/frontend/src/components/Service.js b/priv/frontend/src/components/Service.js index f93cb25..cd9f66b 100644 --- a/priv/frontend/src/components/Service.js +++ b/priv/frontend/src/components/Service.js @@ -7,6 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import './Service.css' import Graph from './Graph.js' import config from '../config.json' +import { truncateToTwoPlaces } from '../util' const { slow_threshold: SLOW_THRESHOLD = 1500 } = config @@ -30,13 +31,21 @@ export function getServiceState(status, latency, threshold = SLOW_THRESHOLD) { return status ? 'alive' : 'dead' } -export default function Service({ graph, name, status, latency, description }) { +export default function Service({ + graph, + name, + status, + latency, + description, + uptime, +}) { const state = getServiceState(status, latency) const title = titles[state] const icon = icons[state] const className = classnames('service', `service-${state}`) + const uptimePercentage = truncateToTwoPlaces(uptime) return (
@@ -47,9 +56,10 @@ export default function Service({ graph, name, status, latency, description }) {

{name}{' '} {latency ? ( - + {Math.round(latency)} - ms + ms ({uptimePercentage} + %) ) : null}

diff --git a/priv/frontend/src/components/__tests__/Service.js b/priv/frontend/src/components/__tests__/Service.js index a1e3a0b..028fc92 100644 --- a/priv/frontend/src/components/__tests__/Service.js +++ b/priv/frontend/src/components/__tests__/Service.js @@ -9,6 +9,7 @@ const props = { name: 'sample service', description: 'a cool service', latency: 50.5, + uptime: 99.9994, } describe('', () => { @@ -26,11 +27,15 @@ describe('', () => { it('renders proper information', () => { const comp = shallow() expect(comp.prop('className')).toEqual('service service-alive') - expect(comp.find('h2.title').text()).toEqual('sample service 51ms') + expect(comp.find('h2.title').text()).toEqual( + 'sample service 51ms (99.9994%)' + ) expect( comp.contains(

a cool service

) ).toEqual(true) - expect(comp.contains(51ms)).toEqual(true) + expect( + comp.contains(51ms (99.9994%)) + ).toEqual(true) }) }) diff --git a/priv/frontend/src/util.js b/priv/frontend/src/util.js index c1774f8..6e00d52 100644 --- a/priv/frontend/src/util.js +++ b/priv/frontend/src/util.js @@ -15,6 +15,11 @@ export function objectFromEntries(entries) { ) } +export function truncateToTwoPlaces(number) { + // https://stackoverflow.com/a/4187164/2491753 + return number.toString().match(/^-?\d+(?:\.\d{0,4})?/)[0] +} + export async function strictFetch(...args) { const resp = await fetch(...args)