Skip to content

Commit

Permalink
Implement Sentry section
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-z committed Jul 15, 2024
1 parent 4686b80 commit 287884e
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 38 deletions.
10 changes: 2 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import useSWR from 'swr'
import './App.css'
import { sentryFetcher } from './utils'
import WATcloudLogo from './assets/watcloud-logo'
import { HealthchecksioStatus } from './healthchecksio'
import { useState } from 'react'
import { SentryStatus } from './sentry'

function App() {
// Parse options from the URL
Expand All @@ -22,10 +21,6 @@ function App() {
}

const [showInternal, setShowInternal] = useState(globalOptions.showInternal);
const { data: sentryDataRaw, error: sentryError, isLoading: sentryIsLoading } = useSWR('/api/0/organizations/watonomous/monitors/', sentryFetcher, { refreshInterval: 5000 });

console.log("==================")
console.log(sentryDataRaw, sentryError, sentryIsLoading);

return (
<>
Expand Down Expand Up @@ -53,8 +48,7 @@ function App() {
</div>
<div className="mb-8">
<h2 className="text-2xl">Sentry</h2>
{sentryIsLoading && <p className='text-gray-500'>Loading...</p>}
{sentryError && <p className='text-red-500'>Error: {sentryError.message}. Is your adblocker blocking the request?</p>}
<SentryStatus {...sentryOptions} />
</div>
</>
)
Expand Down
12 changes: 9 additions & 3 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ export const HEALTHCHECKSIO_API_KEY = 'tCsst0GSKpfvslmpmlsmivRrUCRuv6Iv'
// https://github.com/getsentry/sentry/issues/15310#issuecomment-650443556
export const SENTRY_API_KEY = '53ab13b4337a937bc66eec3a4b3f836b14dd51890fde15fd51b9523eb598382c'

export enum Status {
Good = 'good',
Neutral = 'neutral',
Bad = 'bad',
}

export const STATUS_SYMBOLS = {
good: '🟢',
neutral: '🟡',
bad: '🔴',
[Status.Good]: '🟢',
[Status.Neutral]: '🟡',
[Status.Bad]: '🔴',
} as const;
34 changes: 18 additions & 16 deletions src/healthchecksio.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from "react";
import useSWR from "swr";
import { groupBy, healthchecksioFetcher, removePrefix, removeSuffix, timeSinceShort } from "./utils";
import { STATUS_SYMBOLS } from "./constants";
import { Status, STATUS_SYMBOLS } from "./constants";
import { StatusSummary } from "./summary";
import { OptionGroup } from "./option-group";

Expand Down Expand Up @@ -45,22 +45,24 @@ function processHCData(data: any): HealthchecksioCheck[] | undefined {
});
}

function getStatusSymbol(status: HealthchecksioCheckStatus) {
function getStatus(status: HealthchecksioCheckStatus) {
return {
new: STATUS_SYMBOLS.neutral,
up: STATUS_SYMBOLS.good,
grace: STATUS_SYMBOLS.good,
down: STATUS_SYMBOLS.bad,
paused: '⏸️',
new: Status.Neutral,
up: Status.Good,
grace: Status.Good,
down: Status.Bad,
paused: 'paused',
}[status];
}

function CheckStatus({ status }: { status: HealthchecksioCheckStatus }) {
return (
<span role="img" aria-label={status}>
{getStatusSymbol(status)}
</span>
);
function getStatusSymbol(status: HealthchecksioCheckStatus) {
return {
new: STATUS_SYMBOLS[Status.Neutral],
up: STATUS_SYMBOLS[Status.Good],
grace: STATUS_SYMBOLS[Status.Good],
down: STATUS_SYMBOLS[Status.Bad],
paused: '⏸️',
}[status];
}

const GROUP_KEYS = ['host', 'check'] as const;
Expand All @@ -78,7 +80,7 @@ export function HealthchecksioStatus({ showInternal, initialGroupKey = "" }: { s
const [groupKey, setGroupKey] = useState((GROUP_KEYS.includes(initialGroupKey as any) ? initialGroupKey : GROUP_KEYS[0]) as typeof GROUP_KEYS[number]);
const { data: dataRaw, error, isLoading } = useSWR('/api/v3/checks/', healthchecksioFetcher, { refreshInterval: 5000 });

const checks = (processHCData(dataRaw) || []).filter(check => check.tags_dict.public !== 'False' || showInternal);
const checks = (processHCData(dataRaw) || []).filter(check => check.tags_dict.public !== 'False' || showInternal).sort((a, b) => a.name.localeCompare(b.name));
const groupedChecks = groupBy(checks, c => c.tags_dict[groupKey]);

return (
Expand All @@ -87,7 +89,7 @@ export function HealthchecksioStatus({ showInternal, initialGroupKey = "" }: { s
{error && <p className='text-red-500'>Error: {error.message}</p>}
{checks && checks.length > 0 && (
<>
<StatusSummary symbols={checks.map(check => getStatusSymbol(check.status))} symbolClassName="text-xl" className="mb-4" />
<StatusSummary statuses={checks.map(check => getStatus(check.status))} symbolClassName="text-xl" className="mb-4" />
<div>
<span className="text-sm text-gray-500 flex items-center justify-center mb-1">Group by:</span>
<OptionGroup
Expand All @@ -108,7 +110,7 @@ export function HealthchecksioStatus({ showInternal, initialGroupKey = "" }: { s
<li key={check.name} className="flex justify-between">
<span>{shortenCheckName(groupKey, group, check.name)} <span className="text-sm text-gray-500" aria-label="last update time"
title={`Last updated at ${check.last_ping}`}>{timeSinceShort(new Date(check.last_ping))} ago</span></span>
<CheckStatus status={check.status} />
<span title={check.status}>{getStatusSymbol(check.status)}</span>
</li>
))}
</ul>
Expand Down
59 changes: 59 additions & 0 deletions src/sentry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import useSWR from "swr";
import { sentryFetcher, timeSinceShort } from "./utils";
import { STATUS_SYMBOLS, Status } from "./constants";
import { StatusSummary } from "./summary";

// The API docs just says "string" lol. We can add to this list if we find more
export type SentryStatus = "ok" | "error";

function getStatus(status: SentryStatus) {
return {
ok: Status.Good,
error: Status.Bad,
}[status] || Status.Neutral;
}

function getStatusSymbol(status: SentryStatus) {
return STATUS_SYMBOLS[getStatus(status)];
}

export function SentryStatus() {
const { data: monitors, error, isLoading } = useSWR('/api/0/organizations/watonomous/monitors/', sentryFetcher, { refreshInterval: 5000 });

// process data
if (monitors) {
for (const monitor of monitors) {
// Remove checks for the dev environment
monitor.environments = monitor.environments?.filter((environment: any) => environment.name != 'dev').sort((a: any, b: any) => a.name.localeCompare(b.name));
}
monitors.sort((a: any, b: any) => a.name.localeCompare(b.name));
}

return (
<div className="my-4">
{isLoading && <p className='text-gray-500'>Loading...</p>}
{error && <p className='text-red-500'>Error: {error.message}</p>}
{monitors && monitors.length > 0 && (
<>
<StatusSummary statuses={monitors.flatMap((monitor: any) => monitor.environments?.map((environment: any) => getStatus(environment.status)))} symbolClassName="text-xl" className="mb-4" />
<div className="grid px-8 2xl:grid-cols-4 xl:grid-cols-3 lg:grid-cols-2 sm:grid-cols-2 gap-4">
{monitors.map((monitor: any) => (
<div key={monitor.id} className="border p-4">
<h4 className="text-lg">{monitor.name}</h4>
<ul>
{monitor.environments?.map((environment: any) => (
<li key={environment.name} className="flex justify-between">
<span>{environment.name} <span className="text-sm text-gray-500" aria-label="last update time"
title={`Last updated at ${environment.lastCheckIn}`}>{timeSinceShort(new Date(environment.lastCheckIn))} ago</span></span>
<span title={environment.status}>{getStatusSymbol(environment.status)}</span>
</li>
))}
</ul>
</div>
))}
</div>
</>
)}
</div>
)
}
22 changes: 11 additions & 11 deletions src/summary.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { STATUS_SYMBOLS } from "./constants";
import { Status, STATUS_SYMBOLS } from "./constants";
import { cn } from "./utils";

export function StatusSummary({ symbols, className, symbolClassName }: {
symbols: string[], className?: string, symbolClassName?: string
export function StatusSummary({ statuses, className, symbolClassName }: {
statuses: string[], className?: string, symbolClassName?: string
}) {
const summary = Object.fromEntries(Object.values(STATUS_SYMBOLS).map(symbol => [symbol, 0]));
for (const symbol of symbols) {
if (symbol in summary) {
summary[symbol] += 1;
const summary = statuses.reduce((acc, status) => {
if (Object.values(Status).includes(status as any)) {
acc[status as Status]++;
}
}
return acc;
}, Object.fromEntries(Object.values(Status).map(status => [status, 0])) as Record<Status, number>);

return (
<div className={cn("flex space-x-4 justify-center", className)}>
{Object.entries(summary).map(([symbol, count]) => (
<div key={symbol} className="flex items-center">
<span role="img" aria-label={'good'} className={cn("text-2xl", symbolClassName)}>{symbol}</span>
{Object.entries(summary).map(([status, count]) => (
<div key={status} className="flex items-center">
<span title={status} className={cn("text-2xl", symbolClassName)}>{STATUS_SYMBOLS[status as Status]}</span>
<span className="ml-2">{count}</span>
</div>
))}
Expand Down

0 comments on commit 287884e

Please sign in to comment.