Skip to content

Commit

Permalink
feat: skeleton loading state for alerts table (#2511)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kiryous authored Nov 19, 2024
1 parent b53e502 commit d3e365a
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 141 deletions.
2 changes: 1 addition & 1 deletion keep-ui/app/alerts/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AlertsPage from "../alerts.client";
import AlertsPage from "../alerts";

type PageProps = {
params: { id: string };
Expand Down
11 changes: 10 additions & 1 deletion keep-ui/app/alerts/alert-table-alert-facets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
AddFacetModal,
} from "./alert-table-facet-dynamic";
import { PlusIcon } from "@heroicons/react/24/outline";
import { usePathname } from "next/navigation";

export const AlertFacets: React.FC<AlertFacetsProps> = ({
alerts,
Expand All @@ -26,7 +27,9 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
onDelete,
className,
table,
showSkeleton,
}) => {
const pathname = usePathname();
const timeRangeFilter = table
.getState()
.columnFilters.find((filter) => filter.id === "lastReceived");
Expand All @@ -35,7 +38,7 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
| { start: Date; end: Date; isFromCalendar: boolean }
| undefined;

const presetName = window.location.pathname.split("/").pop() || "default";
const presetName = pathname?.split("/").pop() || "default";

const [isModalOpen, setIsModalOpen] = useLocalStorage<boolean>(
`addFacetModalOpen-${presetName}`,
Expand Down Expand Up @@ -207,6 +210,7 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
}
facetKey="severity"
facetFilters={facetFilters}
showSkeleton={showSkeleton}
/>
<Facet
name="Status"
Expand All @@ -216,6 +220,7 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
}
facetKey="status"
facetFilters={facetFilters}
showSkeleton={showSkeleton}
/>
<Facet
name="Source"
Expand All @@ -225,6 +230,7 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
}
facetKey="source"
facetFilters={facetFilters}
showSkeleton={showSkeleton}
/>
<Facet
name="Assignee"
Expand All @@ -234,6 +240,7 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
}
facetKey="assignee"
facetFilters={facetFilters}
showSkeleton={showSkeleton}
/>
<Facet
name="Dismissed"
Expand All @@ -243,6 +250,7 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
}
facetKey="dismissed"
facetFilters={facetFilters}
showSkeleton={showSkeleton}
/>
<Facet
name="Incident Related"
Expand All @@ -252,6 +260,7 @@ export const AlertFacets: React.FC<AlertFacetsProps> = ({
handleSelect("incident", value, exclusive, isAllOnly)
}
facetFilters={facetFilters}
showSkeleton={showSkeleton}
/>
{/* Dynamic facets */}
{dynamicFacets.map((facet) => (
Expand Down
2 changes: 2 additions & 0 deletions keep-ui/app/alerts/alert-table-facet-types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface FacetProps {
facetKey: string;
facetFilters: FacetFilters;
showIcon?: boolean;
showSkeleton?: boolean;
}

export interface AlertFacetsProps {
Expand All @@ -44,4 +45,5 @@ export interface AlertFacetsProps {
onDelete: (facetKey: string) => void;
className?: string;
table: Table<AlertDto>;
showSkeleton?: boolean;
}
18 changes: 16 additions & 2 deletions keep-ui/app/alerts/alert-table-facet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/20/solid";
import { FacetProps } from "./alert-table-facet-types";
import { FacetValue } from "./alert-table-facet-value";
import { useLocalStorage } from "utils/hooks/useLocalStorage";
import { usePathname } from "next/navigation";
import Skeleton from "react-loading-skeleton";

export const Facet: React.FC<FacetProps> = ({
name,
Expand All @@ -12,9 +14,11 @@ export const Facet: React.FC<FacetProps> = ({
facetKey,
facetFilters,
showIcon = true,
showSkeleton,
}) => {
const pathname = usePathname();
// Get preset name from URL
const presetName = window.location.pathname.split("/").pop() || "default";
const presetName = pathname?.split("/").pop() || "default";

// Store open/close state in localStorage with a unique key per preset and facet
const [isOpen, setIsOpen] = useLocalStorage<boolean>(
Expand Down Expand Up @@ -60,7 +64,17 @@ export const Facet: React.FC<FacetProps> = ({
</div>
)}
<div className="max-h-60 overflow-y-auto">
{values.length > 0 ? (
{showSkeleton ? (
Array.from({ length: 3 }).map((_, index) => (
<div
key={`skeleton-${index}`}
className="flex items-center px-2 py-1 gap-2"
>
<Skeleton containerClassName="h-4 w-4" />
<Skeleton containerClassName="h-4 flex-1" />
</div>
))
) : values.length > 0 ? (
filteredValues.map((value) => (
<FacetValue
key={value.label}
Expand Down
2 changes: 1 addition & 1 deletion keep-ui/app/alerts/alert-table-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export const useAlertTableCols = (
? [
columnHelper.display({
id: "checkbox",
size: 10,
size: 46,
header: (context) => (
<AlertTableCheckbox
checked={context.table.getIsAllRowsSelected()}
Expand Down
19 changes: 5 additions & 14 deletions keep-ui/app/alerts/alert-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,10 @@ export function AlertTable({
return acc.concat(alertId);
}, []);

// show skeleton if no alerts are loaded
let showSkeleton = table.getFilteredRowModel().rows.length === 0;
// if showSkeleton and not loading, show empty state
let showEmptyState = !isAsyncLoading && showSkeleton;
let showSkeleton =
table.getFilteredRowModel().rows.length === 0 && isAsyncLoading;
let showEmptyState =
table.getFilteredRowModel().rows.length === 0 && !isAsyncLoading;

const handleRowClick = (alert: AlertDto) => {
// if presetName is alert-history, do not open sidebar
Expand Down Expand Up @@ -303,21 +303,12 @@ export function AlertTable({
setDynamicFacets={setDynamicFacets}
onDelete={handleFacetDelete}
table={table}
showSkeleton={showSkeleton}
/>
</div>
<div className="flex flex-col gap-4 min-w-0">
<Card className="flex-grow h-full flex flex-col p-0">
<div className="flex-grow">
{isAsyncLoading && (
<Callout
title="Getting your alerts..."
icon={CircleStackIcon}
color="gray"
className="m-5"
>
Alerts will show up in this table as they are added to Keep...
</Callout>
)}
{/* For dynamic preset, add alert tabs*/}
{!presetStatic && (
<AlertTabs
Expand Down
12 changes: 2 additions & 10 deletions keep-ui/app/alerts/alert-tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import { FormEventHandler, useState } from "react";
import {
Button,
TextInput,
Tab,
TabGroup,
TabList,
TabPanel,
TabPanels,
} from "@tremor/react";
import { useState } from "react";
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react";
import { AlertDto } from "./models";
import AlertTabModal from "./alert-tab-modal";
import { evalWithContext } from "./alerts-rules-builder";
Expand Down
30 changes: 25 additions & 5 deletions keep-ui/app/alerts/alerts-table-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ export function AlertsTableBody({
onRowClick(alert);
};


if (showSkeleton) {
return (
<TableBody>
{Array(20)
.fill("")
.map((index, rowIndex) => (
<TableRow key={`row-${index}-${rowIndex}`}>
{table.getAllColumns().map((c, cellIndex) => (
<TableCell
key={clsx(
`cell-${c.id}-${cellIndex}`,
c.columnDef.meta?.tdClassName
)}
>
<Skeleton containerClassName="w-full" />
</TableCell>
))}
</TableRow>
))}
</TableBody>
);
}

return (
<TableBody>
{table.getRowModel().rows.map((row) => {
Expand Down Expand Up @@ -100,11 +124,7 @@ export function AlertsTableBody({
"relative z-[1]" // Ensure cell content is above the border
)}
>
{showSkeleton ? (
<Skeleton />
) : (
flexRender(cell.column.columnDef.cell, cell.getContext())
)}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
);
})}
Expand Down
32 changes: 0 additions & 32 deletions keep-ui/app/alerts/alerts.client.tsx

This file was deleted.

20 changes: 10 additions & 10 deletions keep-ui/app/alerts/alerts.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { useEffect, useMemo, useState } from "react";
import { Preset } from "./models";
import { useAlerts } from "utils/hooks/useAlerts";
Expand All @@ -16,7 +18,8 @@ import { useRouter, useSearchParams } from "next/navigation";
import AlertChangeStatusModal from "./alert-change-status-modal";
import { useAlertPolling } from "utils/hooks/usePusher";
import NotFound from "@/app/not-found";
import NotAuthorized from "@/app/not-authorized";
import { useMounted } from "@/shared/lib/hooks/useMounted";
import { useSession } from "next-auth/react";

const defaultPresets: Preset[] = [
{
Expand Down Expand Up @@ -107,6 +110,11 @@ export default function Alerts({ presetName }: AlertsProps) {
mutate: mutateAlerts,
error: alertsError,
} = usePresetAlerts(selectedPreset ? selectedPreset.name : "");

// const isMounted = useMounted();
const { status: sessionStatus } = useSession();
const isLoading = isAsyncLoading || sessionStatus === "loading";

useEffect(() => {
const fingerprint = searchParams?.get("alertPayloadFingerprint");
if (fingerprint) {
Expand All @@ -126,22 +134,14 @@ export default function Alerts({ presetName }: AlertsProps) {
if (!selectedPreset) {
return <NotFound />;
}
if (alertsError) {
if (alertsError.statusCode === 401) {
console.log("unauthenticated 401");
window.location.href = "/signin";
return null;
}
return <NotAuthorized />;
}

return (
<>
<AlertTableTabPanel
key={selectedPreset.name}
preset={selectedPreset}
alerts={alerts}
isAsyncLoading={isAsyncLoading}
isAsyncLoading={isLoading}
setTicketModalAlert={setTicketModalAlert}
setNoteModalAlert={setNoteModalAlert}
setRunWorkflowModalAlert={setRunWorkflowModalAlert}
Expand Down
19 changes: 14 additions & 5 deletions keep-ui/components/navbar/AlertsLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useLocalStorage } from "utils/hooks/useLocalStorage";
import { ActionMeta, MultiValue } from "react-select";
import { useTags } from "utils/hooks/useTags";
import { usePresets } from "utils/hooks/usePresets";
import { useMounted } from "@/shared/lib/hooks/useMounted";
import clsx from "clsx";

type AlertsLinksProps = {
Expand All @@ -22,6 +23,8 @@ type AlertsLinksProps = {

export const AlertsLinks = ({ session }: AlertsLinksProps) => {
const [isTagModalOpen, setIsTagModalOpen] = useState(false);
const isMounted = useMounted();

const [storedTags, setStoredTags] = useLocalStorage<string[]>(
"selectedTags",
[]
Expand Down Expand Up @@ -59,7 +62,7 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
// Determine if we should show the feed link
const shouldShowFeed = (() => {
// If we have server data, check if feed preset exists
if (staticPresets) {
if (staticPresets.length > 0) {
return staticPresets.some((preset) => preset.name === "feed");
}

Expand All @@ -69,13 +72,19 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
return staticPresetsOrderFromLS?.some((preset) => preset.name === "feed");
}

// If we're still loading (no data and no error), show based on cache
// For the initial render on the server, always show feed
if (!isMounted) {
return true;
}

return staticPresetsOrderFromLS?.some((preset) => preset.name === "feed");
})();

// Get the current alerts count only if we should show feed
const currentAlertsCount = (() => {
if (!shouldShowFeed) return 0;
if (!shouldShowFeed) {
return 0;
}

// First try to get from server data
const serverPreset = staticPresets?.find(
Expand All @@ -89,7 +98,7 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
const cachedPreset = staticPresetsOrderFromLS?.find(
(preset) => preset.name === "feed"
);
return cachedPreset?.alerts_count ?? 0;
return cachedPreset?.alerts_count ?? undefined;
})();

return (
Expand Down Expand Up @@ -141,7 +150,7 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
</LinkWithIcon>
</li>
)}
{session && (
{session && isMounted && (
<CustomPresetAlertLinks
session={session}
selectedTags={storedTags}
Expand Down
Loading

0 comments on commit d3e365a

Please sign in to comment.