From 2fa066ec5a58f37352470adaaa82b0e86087f9f1 Mon Sep 17 00:00:00 2001 From: Vladimir Filonov Date: Thu, 8 Aug 2024 16:41:20 +0400 Subject: [PATCH] fix: poor performance of incidents detail page (#1565) --- .../app/incidents/[id]/incident-alerts.tsx | 54 ++++++++++++++-- keep-ui/app/incidents/[id]/incident.tsx | 2 + keep-ui/app/incidents/incident-pagination.tsx | 61 ++++++++++--------- keep-ui/app/incidents/model.ts | 10 +++ keep-ui/utils/hooks/useIncidents.ts | 8 ++- keep/api/core/db.py | 6 +- keep/api/routes/incidents.py | 15 +++-- keep/api/utils/pagination.py | 6 +- 8 files changed, 116 insertions(+), 46 deletions(-) diff --git a/keep-ui/app/incidents/[id]/incident-alerts.tsx b/keep-ui/app/incidents/[id]/incident-alerts.tsx index a6058720f..d985b2eba 100644 --- a/keep-ui/app/incidents/[id]/incident-alerts.tsx +++ b/keep-ui/app/incidents/[id]/incident-alerts.tsx @@ -26,15 +26,49 @@ import { import AlertName from "app/alerts/alert-name"; import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; import IncidentAlertMenu from "./incident-alert-menu"; +import IncidentPagination from "../incident-pagination"; +import React, {Dispatch, SetStateAction, useEffect, useState} from "react"; interface Props { incidentId: string; } +interface Pagination { + limit: number; + offset: number; +} + + const columnHelper = createColumnHelper(); export default function IncidentAlerts({ incidentId }: Props) { - const { data: alerts } = useIncidentAlerts(incidentId); + const [alertsPagination, setAlertsPagination] = useState({ + limit: 20, + offset: 0, + }); + + const { data: alerts, isLoading } = useIncidentAlerts(incidentId, alertsPagination.limit, alertsPagination.offset); + + const [pagination, setTablePagination] = useState({ + pageIndex: alerts? Math.ceil(alerts.offset / alerts.limit) : 0, + pageSize: alerts? alerts.limit : 20, + }); + + useEffect(() => { + if (alerts && alerts.limit != pagination.pageSize) { + setAlertsPagination({ + limit: pagination.pageSize, + offset: 0, + }) + } + const currentOffset = pagination.pageSize * pagination.pageIndex; + if (alerts && alerts.offset != currentOffset) { + setAlertsPagination({ + limit: pagination.pageSize, + offset: currentOffset, + }) + } + }, [pagination]) usePollIncidentAlerts(incidentId); const columns = [ @@ -104,12 +138,16 @@ export default function IncidentAlerts({ incidentId }: Props) { const table = useReactTable({ columns: columns, - data: alerts ?? [], + manualPagination: true, + state: { pagination }, + rowCount: alerts ? alerts.count : 0, + onPaginationChange: setTablePagination, + data: alerts?.items ?? [], getCoreRowModel: getCoreRowModel(), }); return ( <> - {(alerts ?? []).length === 0 && ( + {!isLoading && (alerts?.items ?? []).length === 0 && ( ))} - {alerts && alerts.length > 0 && ( + {alerts && alerts?.items?.length > 0 && ( {table.getRowModel().rows.map((row) => ( @@ -152,9 +190,9 @@ export default function IncidentAlerts({ incidentId }: Props) { )} { // Skeleton - (alerts ?? []).length === 0 && ( + (isLoading || (alerts?.items ?? []).length === 0) && ( - {Array(5) + {Array(pagination.pageSize) .fill("") .map((index) => ( @@ -169,6 +207,10 @@ export default function IncidentAlerts({ incidentId }: Props) { ) } + +
+ +
); } diff --git a/keep-ui/app/incidents/[id]/incident.tsx b/keep-ui/app/incidents/[id]/incident.tsx index 770db93d9..f41bf2ae2 100644 --- a/keep-ui/app/incidents/[id]/incident.tsx +++ b/keep-ui/app/incidents/[id]/incident.tsx @@ -16,6 +16,7 @@ import { import IncidentAlerts from "./incident-alerts"; import { ArrowUturnLeftIcon } from "@heroicons/react/24/outline"; import { useRouter } from "next/navigation"; +import {useState} from "react"; interface Props { incidentId: string; @@ -23,6 +24,7 @@ interface Props { export default function IncidentView({ incidentId }: Props) { const { data: incident, isLoading, error } = useIncident(incidentId); + const router = useRouter(); if (isLoading || !incident) return ; diff --git a/keep-ui/app/incidents/incident-pagination.tsx b/keep-ui/app/incidents/incident-pagination.tsx index 9c5e0913c..a404dd412 100644 --- a/keep-ui/app/incidents/incident-pagination.tsx +++ b/keep-ui/app/incidents/incident-pagination.tsx @@ -11,9 +11,10 @@ import { StylesConfig, SingleValueProps, components, GroupBase } from 'react-sel import Select from 'react-select'; import { Table } from "@tanstack/react-table"; import {IncidentDto} from "./model"; +import {AlertDto} from "../alerts/models"; interface Props { - table: Table; + table: Table | Table; isRefreshAllowed: boolean; } @@ -22,36 +23,36 @@ interface OptionType { label: string; } - const customStyles: StylesConfig> = { - control: (provided, state) => ({ - ...provided, - borderColor: state.isFocused ? 'orange' : provided.borderColor, - '&:hover': { borderColor: 'orange' }, - boxShadow: state.isFocused ? '0 0 0 1px orange' : provided.boxShadow, - }), - singleValue: (provided) => ({ - ...provided, - display: 'flex', - alignItems: 'center', - }), - menu: (provided) => ({ - ...provided, - color: 'orange', - }), - option: (provided, state) => ({ - ...provided, - backgroundColor: state.isSelected ? 'orange' : provided.backgroundColor, - '&:hover': { backgroundColor: state.isSelected ? 'orange' : '#f5f5f5' }, - color: state.isSelected ? 'white' : provided.color, - }), - }; +const customStyles: StylesConfig> = { + control: (provided, state) => ({ + ...provided, + borderColor: state.isFocused ? 'orange' : provided.borderColor, + '&:hover': { borderColor: 'orange' }, + boxShadow: state.isFocused ? '0 0 0 1px orange' : provided.boxShadow, + }), + singleValue: (provided) => ({ + ...provided, + display: 'flex', + alignItems: 'center', + }), + menu: (provided) => ({ + ...provided, + color: 'orange', + }), + option: (provided, state) => ({ + ...provided, + backgroundColor: state.isSelected ? 'orange' : provided.backgroundColor, + '&:hover': { backgroundColor: state.isSelected ? 'orange' : '#f5f5f5' }, + color: state.isSelected ? 'white' : provided.color, + }), +}; - const SingleValue = ({ children, ...props }: SingleValueProps>) => ( - - {children} - - - ); +const SingleValue = ({ children, ...props }: SingleValueProps>) => ( + + {children} + + +); export default function IncidentPagination({ table, isRefreshAllowed }: Props) { diff --git a/keep-ui/app/incidents/model.ts b/keep-ui/app/incidents/model.ts index d58d08d4c..7a798fadd 100644 --- a/keep-ui/app/incidents/model.ts +++ b/keep-ui/app/incidents/model.ts @@ -1,3 +1,5 @@ +import {AlertDto} from "../alerts/models"; + export interface IncidentDto { id: string; name: string; @@ -18,3 +20,11 @@ export interface PaginatedIncidentsDto { count: number; items: IncidentDto[]; } + +export interface PaginatedIncidentAlertsDto { + limit: number; + offset: number; + count: number; + items: AlertDto[]; +} + diff --git a/keep-ui/utils/hooks/useIncidents.ts b/keep-ui/utils/hooks/useIncidents.ts index 722bd95df..849bf799f 100644 --- a/keep-ui/utils/hooks/useIncidents.ts +++ b/keep-ui/utils/hooks/useIncidents.ts @@ -1,5 +1,5 @@ import { AlertDto } from "app/alerts/models"; -import { IncidentDto, PaginatedIncidentsDto } from "app/incidents/model"; +import {IncidentDto, PaginatedIncidentAlertsDto, PaginatedIncidentsDto} from "app/incidents/model"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; import { getApiURL } from "utils/apiUrl"; @@ -33,14 +33,16 @@ export const useIncidents = ( export const useIncidentAlerts = ( incidentId: string, + limit: number = 20, + offset: number = 0, options: SWRConfiguration = { revalidateOnFocus: false, } ) => { const apiUrl = getApiURL(); const { data: session } = useSession(); - return useSWR( - () => (session ? `${apiUrl}/incidents/${incidentId}/alerts` : null), + return useSWR( + () => (session ? `${apiUrl}/incidents/${incidentId}/alerts?limit=${limit}&offset=${offset}` : null), (url) => fetcher(url, session?.accessToken), options ); diff --git a/keep/api/core/db.py b/keep/api/core/db.py index 8e54b5dd3..3aa893ac8 100644 --- a/keep/api/core/db.py +++ b/keep/api/core/db.py @@ -2082,7 +2082,7 @@ def get_incidents_count( ) -def get_incident_alerts_by_incident_id(tenant_id: str, incident_id: str) -> List[Alert]: +def get_incident_alerts_by_incident_id(tenant_id: str, incident_id: str, limit: int, offset: int) -> (List[Alert], int): with Session(engine) as session: query = ( session.query( @@ -2096,7 +2096,9 @@ def get_incident_alerts_by_incident_id(tenant_id: str, incident_id: str) -> List ) ) - return query.all() + total_count = query.count() + + return query.limit(limit).offset(offset).all(), total_count def get_alerts_data_for_incident( diff --git a/keep/api/routes/incidents.py b/keep/api/routes/incidents.py index 9f817e520..44bb56e37 100644 --- a/keep/api/routes/incidents.py +++ b/keep/api/routes/incidents.py @@ -29,7 +29,7 @@ ) from keep.api.models.alert import AlertDto, IncidentDto, IncidentDtoIn from keep.api.utils.enrichment_helpers import convert_db_alerts_to_dto_alerts -from keep.api.utils.pagination import IncidentsPaginatedResultsDto +from keep.api.utils.pagination import IncidentsPaginatedResultsDto, AlertPaginatedResultsDto router = APIRouter() logger = logging.getLogger(__name__) @@ -229,8 +229,10 @@ def delete_incident( ) def get_incident_alerts( incident_id: str, + limit: int = 25, + offset: int = 0, authenticated_entity: AuthenticatedEntity = Depends(AuthVerifier(["read:alert"])), -) -> List[AlertDto]: +) -> AlertPaginatedResultsDto: tenant_id = authenticated_entity.tenant_id logger.info( "Fetching incident", @@ -250,7 +252,12 @@ def get_incident_alerts( "tenant_id": tenant_id, }, ) - db_alerts = get_incident_alerts_by_incident_id(tenant_id, incident_id) + db_alerts, total_count = get_incident_alerts_by_incident_id( + tenant_id=tenant_id, + incident_id=incident_id, + limit=limit, + offset=offset, + ) enriched_alerts_dto = convert_db_alerts_to_dto_alerts(db_alerts) logger.info( @@ -260,7 +267,7 @@ def get_incident_alerts( }, ) - return enriched_alerts_dto + return AlertPaginatedResultsDto(limit=limit, offset=offset, count=total_count, items=enriched_alerts_dto) @router.post( diff --git a/keep/api/utils/pagination.py b/keep/api/utils/pagination.py index cac63e905..ba33c5256 100644 --- a/keep/api/utils/pagination.py +++ b/keep/api/utils/pagination.py @@ -2,7 +2,7 @@ from pydantic import BaseModel -from keep.api.models.alert import IncidentDto +from keep.api.models.alert import IncidentDto, AlertDto class PaginatedResultsDto(BaseModel): @@ -14,3 +14,7 @@ class PaginatedResultsDto(BaseModel): class IncidentsPaginatedResultsDto(PaginatedResultsDto): items: list[IncidentDto] + + +class AlertPaginatedResultsDto(PaginatedResultsDto): + items: list[AlertDto]