Skip to content

Commit

Permalink
refactor: Status component rebuilt with SWR
Browse files Browse the repository at this point in the history
Main reason is to have only a single ajax request for status data on the
page, no matter how many status icons are embedded.

Also extract the icon portion into its own StatusIcon component that can
be better used in other components, especially inside other anchor tags
  • Loading branch information
kennethkalmer committed Nov 25, 2024
1 parent 76a0d49 commit 1859f99
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 42 deletions.
64 changes: 30 additions & 34 deletions src/core/Status.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { useEffect, useState } from "react";
import React from "react";
import useSWR from "swr";
import clsx from "clsx";

const indicatorClass = (indicator?: string) => {
switch (indicator) {
Expand All @@ -17,53 +19,47 @@ const indicatorClass = (indicator?: string) => {
}
};

export const StatusIcon = ({
statusUrl,
refreshInterval = 1000 * 60,
}: {
statusUrl: string;
refreshInterval?: number;
}) => {
const fetcher = (url: string) => fetch(url).then((res) => res.json());
const { data, error, isLoading } = useSWR(statusUrl, fetcher, {
refreshInterval,
});

return (
<span className="inline-flex items-center h-[1.5rem] p-[0.25rem]">
<span
className={`w-[1rem] h-[1rem] leading-[1rem] rounded-full ${clsx({ "animate-pulse": isLoading || error })} ${indicatorClass(data?.status?.indicator)}`}
></span>
</span>
);
};

const Status = ({
statusUrl,
additionalCSS,
refreshInterval = 1000 * 60,
}: {
statusUrl: string;
additionalCSS?: string;
refreshInterval?: number;
}) => {
const [data, setData] = useState<{ status: { indicator: string } } | null>(
null,
);

useEffect(() => {
let interval: NodeJS.Timeout;

if (statusUrl !== "") {
const fetchData = async () => {
try {
const response = await fetch(statusUrl);
const jsonData = await response.json();
setData(jsonData);
} catch (error) {
console.error("Error fetching status data:", error);
}
};

fetchData();

interval = setInterval(fetchData, 60000); // Fetch data every minute
}

return () => {
clearInterval(interval);
};
}, [statusUrl]);

return (
<a
href="https://status.ably.com"
className={`inline-block ${additionalCSS}`}
target="_blank"
rel="noreferrer"
>
<span className="flex items-center h-[1.5rem] p-[0.25rem]">
<span
className={`w-[1rem] h-[1rem] leading-[1rem] rounded-full ${!data ? "animate-pulse" : ""} ${indicatorClass(data?.status?.indicator)}`}
></span>
</span>
<StatusIcon
statusUrl={statusUrl}
refreshInterval={refreshInterval ?? 1000 * 60}
/>
</a>
);
};
Expand Down
45 changes: 37 additions & 8 deletions src/core/Status/Status.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { delay, http, HttpResponse } from "msw";
import { SWRConfig } from "swr";
import Status from "../Status";

const statusUrl = "https://ntqy1wz94gjv.statuspage.io/api/v2/status.json";
Expand Down Expand Up @@ -29,10 +30,14 @@ export const Loading = {
},
},
},
render: () => <Status statusUrl={statusUrl} />,
render: () => (
<SWRConfig value={{ provider: () => new Map() }}>
<Status statusUrl={statusUrl} refreshInterval={0} />
</SWRConfig>
),
};

const mockParametersWithStatus = (indicator) => {
const mockParametersWithStatus = (indicator: string) => {
return {
msw: {
handlers: {
Expand All @@ -52,30 +57,54 @@ const mockParametersWithStatus = (indicator) => {

export const None = {
parameters: mockParametersWithStatus("none"),
render: () => <Status statusUrl={statusUrl} />,
render: () => (
<SWRConfig value={{ provider: () => new Map() }}>
<Status statusUrl={statusUrl} refreshInterval={0} />
</SWRConfig>
),
};

export const Operational = {
parameters: mockParametersWithStatus("operational"),
render: () => <Status statusUrl={statusUrl} />,
render: () => (
<SWRConfig value={{ provider: () => new Map() }}>
<Status statusUrl={statusUrl} refreshInterval={0} />
</SWRConfig>
),
};

export const Minor = {
parameters: mockParametersWithStatus("minor"),
render: () => <Status statusUrl={statusUrl} />,
render: () => (
<SWRConfig value={{ provider: () => new Map() }}>
<Status statusUrl={statusUrl} refreshInterval={0} />
</SWRConfig>
),
};

export const Major = {
parameters: mockParametersWithStatus("major"),
render: () => <Status statusUrl={statusUrl} />,
render: () => (
<SWRConfig value={{ provider: () => new Map() }}>
<Status statusUrl={statusUrl} refreshInterval={0} />
</SWRConfig>
),
};

export const Critical = {
parameters: mockParametersWithStatus("critical"),
render: () => <Status statusUrl={statusUrl} />,
render: () => (
<SWRConfig value={{ provider: () => new Map() }}>
<Status statusUrl={statusUrl} refreshInterval={0} />
</SWRConfig>
),
};

export const Unknown = {
parameters: mockParametersWithStatus("unknown"),
render: () => <Status statusUrl={statusUrl} />,
render: () => (
<SWRConfig value={{ provider: () => new Map() }}>
<Status statusUrl={statusUrl} refreshInterval={0} />
</SWRConfig>
),
};

0 comments on commit 1859f99

Please sign in to comment.