Skip to content
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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
# NEXT_PUBLIC_INCLUDE_REPORT_ISSUE=0

### Enable dashboard status indicator feature: set to 1 to enable
### When enabled, the E2B status is read from https://status.e2b.dev
### When enabled, the E2B status is read from the incident.io summary API
# NEXT_PUBLIC_INCLUDE_STATUS_INDICATOR=0

### Set to 1 to use mock data
Expand Down
42 changes: 12 additions & 30 deletions src/features/dashboard/layouts/status-indicator.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,23 @@ import { cacheLife } from 'next/cache'
import Link from 'next/link'
import { l } from '@/core/shared/clients/logger/logger'
import { LiveDot } from '@/ui/live'
import {
type AggregateState,
getStatusPageStateFromSummary,
type IncidentIOStatusPageSummaryResponse,
STATUS_PAGE_LINK_URL,
STATUS_PAGE_SUMMARY_URL,
} from './status-indicator'

const STATUS_PAGE_URL = 'https://status.e2b.dev'
const STATUS_PAGE_INDEX_URL = `${STATUS_PAGE_URL}/index.json`
const STATUS_PAGE_FETCH_TIMEOUT_MS = 5_000
const STATUS_PAGE_CACHE_SECONDS = 300

type AggregateState =
| 'operational'
| 'degraded'
| 'downtime'
| 'maintenance'
| 'unknown'

interface StatusPageIndexResponse {
data?: {
attributes?: {
aggregate_state?: string
}
}
}
const STATUS_PAGE_CACHE_SECONDS = 30

interface StatusUI {
label: string
dotCircleClassName: string
dotClassName: string
}

function toAggregateState(value: string | undefined): AggregateState {
if (value === 'operational') return 'operational'
if (value === 'degraded') return 'degraded'
if (value === 'downtime') return 'downtime'
if (value === 'maintenance') return 'maintenance'
return 'unknown'
}

function getStatusUI(state: AggregateState): StatusUI {
switch (state) {
case 'operational':
Expand Down Expand Up @@ -83,7 +65,7 @@ async function getStatusPageState(): Promise<AggregateState> {
})

try {
const response = await fetch(STATUS_PAGE_INDEX_URL, {
const response = await fetch(STATUS_PAGE_SUMMARY_URL, {
cache: 'force-cache',
next: { revalidate: STATUS_PAGE_CACHE_SECONDS },
signal: AbortSignal.timeout(STATUS_PAGE_FETCH_TIMEOUT_MS),
Expand All @@ -101,8 +83,8 @@ async function getStatusPageState(): Promise<AggregateState> {
return 'unknown'
}

const data = (await response.json()) as StatusPageIndexResponse
return toAggregateState(data.data?.attributes?.aggregate_state)
const data = (await response.json()) as IncidentIOStatusPageSummaryResponse
return getStatusPageStateFromSummary(data)
} catch {
return 'unknown'
}
Expand All @@ -114,7 +96,7 @@ export default async function DashboardStatusBadgeServer() {

return (
<Link
href={STATUS_PAGE_URL}
href={STATUS_PAGE_LINK_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-flex h-5 shrink-0 items-center gap-1.5"
Expand Down
107 changes: 107 additions & 0 deletions src/features/dashboard/layouts/status-indicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
export type AggregateState =
| 'operational'
| 'degraded'
| 'downtime'
| 'maintenance'
| 'unknown'

export interface IncidentIOStatusPageSummaryResponse {
status?: {
indicator?: string
}
components?: Array<{
status?: string
}>
scheduled_maintenances?: Array<{
status?: string
}>
}

export const STATUS_PAGE_LINK_URL = 'https://status.e2b.dev'
const INCIDENT_IO_STATUS_PAGE_URL = 'https://statuspage.incident.io/e2b-service'
export const STATUS_PAGE_SUMMARY_URL = `${INCIDENT_IO_STATUS_PAGE_URL}/api/v2/summary.json`

const STATUS_PRIORITY: Record<AggregateState, number> = {
unknown: 0,
operational: 1,
maintenance: 2,
degraded: 3,
downtime: 4,
}

const INDICATOR_STATE: Record<string, AggregateState> = {
none: 'operational',
minor: 'degraded',
major: 'degraded',
critical: 'downtime',
maintenance: 'maintenance',
}

const COMPONENT_STATE: Record<string, AggregateState> = {
operational: 'operational',
under_maintenance: 'maintenance',
degraded_performance: 'degraded',
partial_outage: 'degraded',
full_outage: 'downtime',
major_outage: 'downtime',
}

const MAINTENANCE_IN_PROGRESS_STATUSES = new Set([
'in_progress',
'maintenance_in_progress',
])

function stateFromValue(
value: string | undefined,
stateMap: Record<string, AggregateState>
) {
return value ? stateMap[value] : undefined
}

function highestPriorityState(
states: Array<AggregateState | undefined>
): AggregateState | undefined {
return states.reduce<AggregateState | undefined>((highest, state) => {
if (!state) return highest
if (!highest) return state

return STATUS_PRIORITY[state] > STATUS_PRIORITY[highest] ? state : highest
}, undefined)
}

function getWorstComponentState(
components: IncidentIOStatusPageSummaryResponse['components']
): AggregateState | undefined {
return highestPriorityState(
components?.map((component) =>
stateFromValue(component.status, COMPONENT_STATE)
) ?? []
)
}

function hasMaintenanceInProgress(
maintenances: IncidentIOStatusPageSummaryResponse['scheduled_maintenances']
): boolean {
return (
maintenances?.some(
(maintenance) =>
!!maintenance.status &&
MAINTENANCE_IN_PROGRESS_STATUSES.has(maintenance.status)
) ?? false
)
}

export function getStatusPageStateFromSummary(
data: IncidentIOStatusPageSummaryResponse
): AggregateState {
const indicatorState = stateFromValue(data.status?.indicator, INDICATOR_STATE)
const componentState = getWorstComponentState(data.components)
const maintenanceState = hasMaintenanceInProgress(data.scheduled_maintenances)
? 'maintenance'
: undefined

return (
highestPriorityState([indicatorState, componentState, maintenanceState]) ??
'unknown'
)
}
161 changes: 161 additions & 0 deletions tests/unit/status-indicator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { describe, expect, it } from 'vitest'
import { getStatusPageStateFromSummary } from '@/features/dashboard/layouts/status-indicator'

describe('status-indicator', () => {
it('should report operational for summary indicator none', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'none',
},
})
).toBe('operational')
})

it('should report maintenance for in-progress maintenance', () => {
expect(
getStatusPageStateFromSummary({
scheduled_maintenances: [
{
status: 'in_progress',
},
],
})
).toBe('maintenance')
})

it('should support incident.io maintenance status naming', () => {
expect(
getStatusPageStateFromSummary({
scheduled_maintenances: [
{
status: 'maintenance_in_progress',
},
],
})
).toBe('maintenance')
})

it('should prioritize critical indicator over maintenance', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'critical',
},
scheduled_maintenances: [
{
status: 'in_progress',
},
],
})
).toBe('downtime')
})

it('should report downtime for full outage components', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'none',
},
components: [
{
status: 'degraded_performance',
},
{
status: 'full_outage',
},
],
})
).toBe('downtime')
})

it('should support major outage as a Statuspage compatibility alias', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'none',
},
components: [
{
status: 'major_outage',
},
],
})
).toBe('downtime')
})

it('should report degraded for partial outage components', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'none',
},
components: [
{
status: 'partial_outage',
},
],
})
).toBe('degraded')
})

it('should report maintenance for under maintenance components', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'none',
},
components: [
{
status: 'under_maintenance',
},
],
})
).toBe('maintenance')
})

it('should prioritize degraded components over maintenance indicator', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'maintenance',
},
components: [
{
status: 'degraded_performance',
},
],
})
).toBe('degraded')
})

it('should report downtime for critical summary indicator', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'critical',
},
})
).toBe('downtime')
})

it('should report degraded for major summary indicator', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'major',
},
})
).toBe('degraded')
})

it('should report degraded for minor summary indicator', () => {
expect(
getStatusPageStateFromSummary({
status: {
indicator: 'minor',
},
})
).toBe('degraded')
})
})
Loading