Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: skeleton loading state for alerts table #2511

Merged
merged 14 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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 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 @@ -248,10 +248,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 @@ -301,21 +301,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
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
11 changes: 7 additions & 4 deletions keep-ui/components/navbar/AlertsLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ActionMeta, MultiValue } from "react-select";
import { useTags } from "utils/hooks/useTags";
import { usePresets } from "utils/hooks/usePresets";
import classNames from "classnames";
import { useMounted } from "@/shared/lib/hooks/useMounted";

type AlertsLinksProps = {
session: Session | null;
Expand Down Expand Up @@ -59,7 +60,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 @@ -70,7 +71,7 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
}

// If we're still loading (no data and no error), show based on cache
return staticPresetsOrderFromLS?.some((preset) => preset.name === "feed");
return true;
})();

// Get the current alerts count only if we should show feed
Expand All @@ -89,9 +90,11 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
const cachedPreset = staticPresetsOrderFromLS?.find(
(preset) => preset.name === "feed"
);
return cachedPreset?.alerts_count ?? 0;
return cachedPreset?.alerts_count ?? undefined;
})();

const isMounted = useMounted();

return (
<>
<Disclosure as="div" className="space-y-1" defaultOpen>
Expand Down Expand Up @@ -141,7 +144,7 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
</LinkWithIcon>
</li>
)}
{session && (
{session && isMounted && (
<CustomPresetAlertLinks
session={session}
selectedTags={storedTags}
Expand Down
11 changes: 11 additions & 0 deletions keep-ui/shared/lib/hooks/useMounted.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useState, useEffect } from "react";

export function useMounted() {
const [isMounted, setIsMounted] = useState(false);

useEffect(() => {
setIsMounted(true);
}, []);

return isMounted;
}
Kiryous marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading