prettier
This commit is contained in:
parent
a04d44e7de
commit
190f461706
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"arrowParens": "always",
|
||||||
|
"proseWrap": "always"
|
||||||
|
}
|
|
@ -5,11 +5,11 @@ import { Router } from '@reach/router'
|
||||||
import Dashboard from './Dashboard'
|
import Dashboard from './Dashboard'
|
||||||
import Incidents from './Incidents'
|
import Incidents from './Incidents'
|
||||||
|
|
||||||
export default function App () {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Dashboard path="/"/>
|
<Dashboard path="/" />
|
||||||
<Incidents path="/incidents/"/>
|
<Incidents path="/incidents/" />
|
||||||
</Router>
|
</Router>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default class Dashboard extends Component {
|
||||||
incident: null,
|
incident: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount () {
|
async componentDidMount() {
|
||||||
try {
|
try {
|
||||||
await this.loadMetrics()
|
await this.loadMetrics()
|
||||||
await this.loadIncident()
|
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
|
const { status: statuses } = this.state.metrics
|
||||||
log('updating status on:', name)
|
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
|
const { metrics } = this.state
|
||||||
|
|
||||||
log('adding latency entry:', data)
|
log('adding latency entry:', data)
|
||||||
|
@ -97,50 +97,50 @@ export default class Dashboard extends Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMetrics () {
|
async loadMetrics() {
|
||||||
log('loading metrics')
|
log('loading metrics')
|
||||||
|
|
||||||
const resp = await strictFetch(`${DOMAIN}/api/status`)
|
const resp = await strictFetch(`${DOMAIN}/api/status`)
|
||||||
this.setState({ metrics: await resp.json() })
|
this.setState({ metrics: await resp.json() })
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadIncident () {
|
async loadIncident() {
|
||||||
log('loading current incident')
|
log('loading current incident')
|
||||||
|
|
||||||
const resp = await strictFetch(`${DOMAIN}/api/incidents/current`)
|
const resp = await strictFetch(`${DOMAIN}/api/incidents/current`)
|
||||||
this.setState({ incident: await resp.json() })
|
this.setState({ incident: await resp.json() })
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNotice (services) {
|
renderNotice(services) {
|
||||||
const down = services.filter(([, { status }]) => !status)
|
const down = services.filter(([, { status }]) => !status)
|
||||||
|
|
||||||
// DegradedNotice should only be shown when there is no ongoing incident,
|
// DegradedNotice should only be shown when there is no ongoing incident,
|
||||||
// and any services are reported as down.
|
// and any services are reported as down.
|
||||||
if (!this.state.incident && down.length > 0) {
|
if (!this.state.incident && down.length > 0) {
|
||||||
return <DegradedNotice services={objectFromEntries(down)}/>
|
return <DegradedNotice services={objectFromEntries(down)} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Status incident={this.state.incident}/>
|
return <Status incident={this.state.incident} />
|
||||||
}
|
}
|
||||||
|
|
||||||
renderServices (services) {
|
renderServices(services) {
|
||||||
const { graph: graphs } = this.state.metrics
|
const { graph: graphs } = this.state.metrics
|
||||||
return services.map(([name, info]) => (
|
return services.map(([name, info]) => (
|
||||||
<Service name={name} key={name} graph={graphs[name]} {...info}/>
|
<Service name={name} key={name} graph={graphs[name]} {...info} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPlaceholders () {
|
renderPlaceholders() {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ServicePlaceholder/>
|
<ServicePlaceholder />
|
||||||
<ServicePlaceholder/>
|
<ServicePlaceholder />
|
||||||
<ServicePlaceholder/>
|
<ServicePlaceholder />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDashboardContent () {
|
renderDashboardContent() {
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
return <div className="error">{this.state.error}</div>
|
return <div className="error">{this.state.error}</div>
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ export default class Dashboard extends Component {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<h1>elstatus</h1>
|
<h1>elstatus</h1>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import './DegradedNotice.css'
|
import './DegradedNotice.css'
|
||||||
|
|
||||||
export default function DegradedNotice ({ services }) {
|
export default function DegradedNotice({ services }) {
|
||||||
const keys = Object.keys(services)
|
const keys = Object.keys(services)
|
||||||
const serviceNames = keys.join(', ')
|
const serviceNames = keys.join(', ')
|
||||||
|
|
||||||
|
@ -12,7 +12,10 @@ export default function DegradedNotice ({ services }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="degraded-notice">
|
<div className="degraded-notice">
|
||||||
<header>{keys.length} service{plural} {indicative} unreachable</header>
|
<header>
|
||||||
|
{keys.length} service
|
||||||
|
{plural} {indicative} unreachable
|
||||||
|
</header>
|
||||||
<p>
|
<p>
|
||||||
elstat is having trouble contacting <strong>{serviceNames}</strong>.
|
elstat is having trouble contacting <strong>{serviceNames}</strong>.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -24,11 +24,11 @@ export default class Graph extends Component {
|
||||||
screenWidth: window.innerWidth,
|
screenWidth: window.innerWidth,
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
window.addEventListener('resize', this.handleScreenChange)
|
window.addEventListener('resize', this.handleScreenChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.handleScreenChange)
|
window.removeEventListener('resize', this.handleScreenChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ export default class Graph extends Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
processData () {
|
processData() {
|
||||||
const { data } = this.props
|
const { data } = this.props
|
||||||
|
|
||||||
const objects = data.map(([timestamp, latency]) => ({
|
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)
|
return objects.sort(({ timestamp: a }, { timestamp: b }) => a - b)
|
||||||
}
|
}
|
||||||
|
|
||||||
isSmallScreen () {
|
isSmallScreen() {
|
||||||
return this.state.screenWidth < 500
|
return this.state.screenWidth < 500
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const yAxis = this.isSmallScreen()
|
const yAxis = this.isSmallScreen() ? null : (
|
||||||
? null
|
<YAxis
|
||||||
: (
|
dataKey="latency"
|
||||||
<YAxis
|
tickLine={false}
|
||||||
dataKey="latency"
|
tickFormatter={(tick) => `${tick}ms`}
|
||||||
tickLine={false}
|
/>
|
||||||
tickFormatter={(tick) => `${tick}ms`}
|
)
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="graph-container">
|
<div className="graph-container">
|
||||||
<ResponsiveContainer width="100%" height={175}>
|
<ResponsiveContainer width="100%" height={175}>
|
||||||
<AreaChart
|
<AreaChart data={this.processData()}>
|
||||||
data={this.processData()}
|
|
||||||
>
|
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="timestamp"
|
dataKey="timestamp"
|
||||||
tickFormatter={(tick) => ms(Date.now() - tick)}
|
tickFormatter={(tick) => ms(Date.now() - tick)}
|
||||||
|
@ -78,7 +74,7 @@ export default class Graph extends Component {
|
||||||
/>
|
/>
|
||||||
{yAxis}
|
{yAxis}
|
||||||
|
|
||||||
<CartesianGrid strokeDasharray="1 1"/>
|
<CartesianGrid strokeDasharray="1 1" />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
isAnimationActive={false}
|
isAnimationActive={false}
|
||||||
formatter={(value) => `${value}ms`}
|
formatter={(value) => `${value}ms`}
|
||||||
|
@ -86,11 +82,7 @@ export default class Graph extends Component {
|
||||||
separator=": "
|
separator=": "
|
||||||
labelFormatter={() => null}
|
labelFormatter={() => null}
|
||||||
/>
|
/>
|
||||||
<ReferenceLine
|
<ReferenceLine y={1000} label="1s" stroke="pink" />
|
||||||
y={1000}
|
|
||||||
label="1s"
|
|
||||||
stroke="pink"
|
|
||||||
/>
|
|
||||||
<Area
|
<Area
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="latency"
|
dataKey="latency"
|
||||||
|
|
|
@ -12,36 +12,39 @@ const OUTAGE_TYPES = {
|
||||||
degraded_service: 'Degraded Service',
|
degraded_service: 'Degraded Service',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Incident ({ incident }) {
|
export default function Incident({ incident }) {
|
||||||
const { content, end_date: end, start_date: start, title, type, stages } = incident
|
const {
|
||||||
|
content,
|
||||||
|
end_date: end,
|
||||||
|
start_date: start,
|
||||||
|
title,
|
||||||
|
type,
|
||||||
|
stages,
|
||||||
|
} = incident
|
||||||
|
|
||||||
const startDate = new Date(start * 1000)
|
const startDate = new Date(start * 1000)
|
||||||
const endDate = end ? new Date(end * 1000) : null
|
const endDate = end ? new Date(end * 1000) : null
|
||||||
const tooltip = `Started ${startDate.toLocaleString()}` +
|
const tooltip =
|
||||||
|
`Started ${startDate.toLocaleString()}` +
|
||||||
(endDate ? `, ended ${endDate.toLocaleString()}` : '')
|
(endDate ? `, ended ${endDate.toLocaleString()}` : '')
|
||||||
const ago = ms(Date.now() - start * 1000)
|
const ago = ms(Date.now() - start * 1000)
|
||||||
const agoEnd = end ? ms(Date.now() - end * 1000) : null
|
const agoEnd = end ? ms(Date.now() - end * 1000) : null
|
||||||
|
|
||||||
const stageNodes = stages.map(
|
const stageNodes = stages
|
||||||
(stage) => <Stage key={stage.title} stage={stage}/>
|
.map((stage) => <Stage key={stage.title} stage={stage} />)
|
||||||
).reverse() // show newest first
|
.reverse() // show newest first
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="incident">
|
<div className="incident">
|
||||||
<h2>{OUTAGE_TYPES[type] || type}: {title}</h2>
|
<h2>
|
||||||
|
{OUTAGE_TYPES[type] || type}: {title}
|
||||||
|
</h2>
|
||||||
<p>{content}</p>
|
<p>{content}</p>
|
||||||
<footer title={tooltip}>
|
<footer title={tooltip}>
|
||||||
Started {ago} ago
|
Started {ago} ago
|
||||||
{end ? `, ended ${agoEnd} ago` : null}
|
{end ? `, ended ${agoEnd} ago` : null}
|
||||||
</footer>
|
</footer>
|
||||||
{stageNodes.length
|
{stageNodes.length ? <div className="stages">{stageNodes}</div> : null}
|
||||||
? (
|
|
||||||
<div className="stages">
|
|
||||||
{stageNodes}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,26 +11,26 @@ export default class Incidents extends React.Component {
|
||||||
page: 0,
|
page: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.fetchIncidents()
|
this.fetchIncidents()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchIncidents () {
|
async fetchIncidents() {
|
||||||
const resp = await strictFetch(`${DOMAIN}/api/incidents/${this.state.page}`)
|
const resp = await strictFetch(`${DOMAIN}/api/incidents/${this.state.page}`)
|
||||||
this.setState({ incidents: await resp.json() })
|
this.setState({ incidents: await resp.json() })
|
||||||
}
|
}
|
||||||
|
|
||||||
renderIncidents () {
|
renderIncidents() {
|
||||||
if (!this.state.incidents) {
|
if (!this.state.incidents) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.state.incidents.map(
|
return this.state.incidents.map((incident) => (
|
||||||
(incident) => <Incident key={incident.id} incident={incident}/>
|
<Incident key={incident.id} incident={incident} />
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<h1>Incidents</h1>
|
<h1>Incidents</h1>
|
||||||
|
|
|
@ -3,12 +3,8 @@ import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import './Page.css'
|
import './Page.css'
|
||||||
|
|
||||||
export default function Page ({ children }) {
|
export default function Page({ children }) {
|
||||||
return (
|
return <div className="page">{children}</div>
|
||||||
<div className="page">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Page.propTypes = {
|
Page.propTypes = {
|
||||||
|
|
|
@ -32,13 +32,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.service.service-alive .emoji {
|
.service.service-alive .emoji {
|
||||||
color: #2ECC40;
|
color: #2ecc40;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service.service-slow .emoji {
|
.service.service-slow .emoji {
|
||||||
color: #FF851B;
|
color: #ff851b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service.service-dead .emoji {
|
.service.service-dead .emoji {
|
||||||
color: #FF4136;
|
color: #ff4136;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,7 @@ import './Service.css'
|
||||||
import Graph from './Graph.js'
|
import Graph from './Graph.js'
|
||||||
import config from '../config.json'
|
import config from '../config.json'
|
||||||
|
|
||||||
const {
|
const { slow_threshold: SLOW_THRESHOLD = 1500 } = config
|
||||||
slow_threshold: SLOW_THRESHOLD = 1500,
|
|
||||||
} = config
|
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
alive: 'check-circle',
|
alive: 'check-circle',
|
||||||
|
@ -24,7 +22,7 @@ const titles = {
|
||||||
dead: 'This service is unresponsive.',
|
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) {
|
if (status && latency > threshold) {
|
||||||
return 'slow'
|
return 'slow'
|
||||||
}
|
}
|
||||||
|
@ -32,35 +30,32 @@ export function getServiceState (status, latency, threshold = SLOW_THRESHOLD) {
|
||||||
return status ? 'alive' : 'dead'
|
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 state = getServiceState(status, latency)
|
||||||
|
|
||||||
const title = titles[state]
|
const title = titles[state]
|
||||||
const icon = icons[state]
|
const icon = icons[state]
|
||||||
|
|
||||||
const className = classnames(
|
const className = classnames('service', `service-${state}`)
|
||||||
'service',
|
|
||||||
`service-${state}`
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<header>
|
<header>
|
||||||
<div className="emoji" title={title}>
|
<div className="emoji" title={title}>
|
||||||
<FontAwesomeIcon title={title} icon={icon}/>
|
<FontAwesomeIcon title={title} icon={icon} />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="title">
|
<h2 className="title">
|
||||||
{name} {latency ? (
|
{name}{' '}
|
||||||
|
{latency ? (
|
||||||
<span className="latency">
|
<span className="latency">
|
||||||
{Math.round(latency)}ms
|
{Math.round(latency)}
|
||||||
|
ms
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</h2>
|
</h2>
|
||||||
</header>
|
</header>
|
||||||
<p className="description">
|
<p className="description">{description}</p>
|
||||||
{description}
|
{graph ? <Graph data={graph} /> : null}
|
||||||
</p>
|
|
||||||
{graph ? <Graph data={graph}/> : null}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -71,9 +66,7 @@ Service.defaultProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
Service.propTypes = {
|
Service.propTypes = {
|
||||||
graph: PropTypes.arrayOf(
|
graph: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
|
||||||
PropTypes.arrayOf(PropTypes.number),
|
|
||||||
),
|
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
status: PropTypes.bool.isRequired,
|
status: PropTypes.bool.isRequired,
|
||||||
latency: PropTypes.number,
|
latency: PropTypes.number,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'react-placeholder/lib/reactPlaceholder.css'
|
||||||
|
|
||||||
import './ServicePlaceholder.css'
|
import './ServicePlaceholder.css'
|
||||||
|
|
||||||
export default function ServicePlaceholder () {
|
export default function ServicePlaceholder() {
|
||||||
return (
|
return (
|
||||||
<div className="service-placeholder">
|
<div className="service-placeholder">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import ms from 'ms'
|
import ms from 'ms'
|
||||||
|
|
||||||
export default function Stage ({ stage }) {
|
export default function Stage({ stage }) {
|
||||||
const { created_at: createdAt, title, content } = stage
|
const { created_at: createdAt, title, content } = stage
|
||||||
const ago = ms(Date.now() - createdAt * 1000)
|
const ago = ms(Date.now() - createdAt * 1000)
|
||||||
|
|
||||||
|
|
|
@ -5,21 +5,19 @@ import classnames from 'classnames'
|
||||||
import './Status.css'
|
import './Status.css'
|
||||||
import Incident from './Incident'
|
import Incident from './Incident'
|
||||||
|
|
||||||
export default function Status ({ incident }) {
|
export default function Status({ incident }) {
|
||||||
const incidentOngoing = incident && incident.ongoing === 1
|
const incidentOngoing = incident && incident.ongoing === 1
|
||||||
const view = incidentOngoing
|
const view = incidentOngoing ? (
|
||||||
? <Incident incident={incident}/>
|
<Incident incident={incident} />
|
||||||
: 'All systems operational'
|
) : (
|
||||||
|
'All systems operational'
|
||||||
|
)
|
||||||
|
|
||||||
const className = classnames(
|
const className = classnames(
|
||||||
'status',
|
'status',
|
||||||
incidentOngoing ? 'status-bad' : 'status-good',
|
incidentOngoing ? 'status-bad' : 'status-good'
|
||||||
)
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
{view}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
|
return <div className={className}>{view}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
Status.propTypes = {
|
Status.propTypes = {
|
||||||
|
|
|
@ -4,23 +4,34 @@ import { shallow } from 'enzyme'
|
||||||
import DegradedNotice from '../DegradedNotice'
|
import DegradedNotice from '../DegradedNotice'
|
||||||
|
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
shallow(<DegradedNotice services={{ first: null, second: null }}/>)
|
shallow(<DegradedNotice services={{ first: null, second: null }} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows one service', () => {
|
it('shows one service', () => {
|
||||||
const comp = shallow(<DegradedNotice services={{ first: null }}/>)
|
const comp = shallow(<DegradedNotice services={{ first: null }} />)
|
||||||
expect(comp.contains(<header>1 service is unreachable</header>)).toEqual(true)
|
expect(comp.contains(<header>1 service is unreachable</header>)).toEqual(true)
|
||||||
expect(comp.contains(
|
expect(
|
||||||
<p>elstat is having trouble contacting <strong>first</strong>.</p>
|
comp.contains(
|
||||||
)).toEqual(true)
|
<p>
|
||||||
|
elstat is having trouble contacting <strong>first</strong>.
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows multiple services', () => {
|
it('shows multiple services', () => {
|
||||||
const comp = shallow(<DegradedNotice services={{ first: null, second: null, third: null }}/>)
|
const comp = shallow(
|
||||||
expect(comp.contains(
|
<DegradedNotice services={{ first: null, second: null, third: null }} />
|
||||||
<header>3 services are unreachable</header>
|
)
|
||||||
)).toEqual(true)
|
expect(comp.contains(<header>3 services are unreachable</header>)).toEqual(
|
||||||
expect(comp.contains(
|
true
|
||||||
<p>elstat is having trouble contacting <strong>first, second, third</strong>.</p>
|
)
|
||||||
)).toEqual(true)
|
expect(
|
||||||
|
comp.contains(
|
||||||
|
<p>
|
||||||
|
elstat is having trouble contacting{' '}
|
||||||
|
<strong>first, second, third</strong>.
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,11 +3,7 @@ import { shallow } from 'enzyme'
|
||||||
|
|
||||||
import Service, { getServiceState } from '../Service'
|
import Service, { getServiceState } from '../Service'
|
||||||
|
|
||||||
const graph = [
|
const graph = [[1000, 50], [2000, 30], [3000, 60]]
|
||||||
[1000, 50],
|
|
||||||
[2000, 30],
|
|
||||||
[3000, 60],
|
|
||||||
]
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
name: 'sample service',
|
name: 'sample service',
|
||||||
|
@ -17,19 +13,23 @@ const props = {
|
||||||
|
|
||||||
describe('<Service/>', () => {
|
describe('<Service/>', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
shallow(<Service graph={null} status {...props}/>)
|
shallow(<Service graph={null} status {...props} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('omits information', () => {
|
it('omits information', () => {
|
||||||
const comp = shallow(<Service graph={null} status {...props} latency={null}/>)
|
const comp = shallow(
|
||||||
|
<Service graph={null} status {...props} latency={null} />
|
||||||
|
)
|
||||||
expect(comp.find('h2.title').text()).toEqual('sample service ')
|
expect(comp.find('h2.title').text()).toEqual('sample service ')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders proper information', () => {
|
it('renders proper information', () => {
|
||||||
const comp = shallow(<Service graph={graph} status {...props}/>)
|
const comp = shallow(<Service graph={graph} status {...props} />)
|
||||||
expect(comp.prop('className')).toEqual('service service-alive')
|
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')
|
||||||
expect(comp.contains(<p className="description">a cool service</p>)).toEqual(true)
|
expect(
|
||||||
|
comp.contains(<p className="description">a cool service</p>)
|
||||||
|
).toEqual(true)
|
||||||
expect(comp.contains(<span className="latency">51ms</span>)).toEqual(true)
|
expect(comp.contains(<span className="latency">51ms</span>)).toEqual(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,10 +5,6 @@ import {
|
||||||
faExclamationCircle,
|
faExclamationCircle,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
export default function register () {
|
export default function register() {
|
||||||
library.add(
|
library.add(faCheckCircle, faTimesCircle, faExclamationCircle)
|
||||||
faCheckCircle,
|
|
||||||
faTimesCircle,
|
|
||||||
faExclamationCircle,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,4 @@ import register from './icons.js'
|
||||||
|
|
||||||
register()
|
register()
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(<App />, document.getElementById('root'))
|
||||||
<App/>,
|
|
||||||
document.getElementById('root')
|
|
||||||
)
|
|
||||||
|
|
|
@ -8,14 +8,14 @@ export const logger = ({ name, color }) => (...args) =>
|
||||||
|
|
||||||
export const log = logger({ name: 'elstat', color: 'purple' })
|
export const log = logger({ name: 'elstat', color: 'purple' })
|
||||||
|
|
||||||
export function objectFromEntries (entries) {
|
export function objectFromEntries(entries) {
|
||||||
return entries.reduce(
|
return entries.reduce(
|
||||||
(object, [key, value]) => ({ ...object, [key]: value }),
|
(object, [key, value]) => ({ ...object, [key]: value }),
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function strictFetch (...args) {
|
export async function strictFetch(...args) {
|
||||||
const resp = await fetch(...args)
|
const resp = await fetch(...args)
|
||||||
|
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { logger } from '../util'
|
||||||
const log = logger({ name: 'ws', color: 'green' })
|
const log = logger({ name: 'ws', color: 'green' })
|
||||||
|
|
||||||
export default class StreamingClient extends NanoEvents {
|
export default class StreamingClient extends NanoEvents {
|
||||||
constructor (url, metrics) {
|
constructor(url, metrics) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.url = url
|
this.url = url
|
||||||
|
@ -14,11 +14,11 @@ export default class StreamingClient extends NanoEvents {
|
||||||
this.delay = 1000
|
this.delay = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
send (packet) {
|
send(packet) {
|
||||||
this.ws.send(JSON.stringify(packet))
|
this.ws.send(JSON.stringify(packet))
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe () {
|
subscribe() {
|
||||||
if (!this.metrics) {
|
if (!this.metrics) {
|
||||||
log('not subscribing to channels -- no initial metrics')
|
log('not subscribing to channels -- no initial metrics')
|
||||||
return
|
return
|
||||||
|
@ -36,7 +36,7 @@ export default class StreamingClient extends NanoEvents {
|
||||||
this.send({ op: OP.SUBSCRIBE, channels })
|
this.send({ op: OP.SUBSCRIBE, channels })
|
||||||
}
|
}
|
||||||
|
|
||||||
handle (packet) {
|
handle(packet) {
|
||||||
const { op, c: channel, d: data } = packet
|
const { op, c: channel, d: data } = packet
|
||||||
|
|
||||||
if ([OP.SUBSCRIBED, OP.UNSUBSCRIBED].includes(op)) {
|
if ([OP.SUBSCRIBED, OP.UNSUBSCRIBED].includes(op)) {
|
||||||
|
@ -52,11 +52,11 @@ export default class StreamingClient extends NanoEvents {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connect () {
|
connect() {
|
||||||
const begin = Date.now()
|
const begin = Date.now()
|
||||||
log('connecting')
|
log('connecting')
|
||||||
|
|
||||||
const ws = this.ws = new WebSocket(this.url)
|
const ws = (this.ws = new WebSocket(this.url))
|
||||||
window.ws = ws
|
window.ws = ws
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
|
@ -68,7 +68,7 @@ export default class StreamingClient extends NanoEvents {
|
||||||
ws.onclose = ({ code, reason }) => {
|
ws.onclose = ({ code, reason }) => {
|
||||||
log(
|
log(
|
||||||
`ws closed with code ${code} (reason: ${reason || '<none>'}); ` +
|
`ws closed with code ${code} (reason: ${reason || '<none>'}); ` +
|
||||||
`attempting to reconnect in ${this.delay}ms`
|
`attempting to reconnect in ${this.delay}ms`
|
||||||
)
|
)
|
||||||
|
|
||||||
setTimeout(() => this.connect(), this.delay)
|
setTimeout(() => this.connect(), this.delay)
|
||||||
|
|
Loading…
Reference in New Issue