From 77b4e71543a0cdbf27a3c0fe16d4a83c7ed241a6 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 15 Dec 2023 17:23:01 +0530 Subject: [PATCH] [refactor]: persistance of sorting and page in table (#4221) --- .../ResizeTable/DynamicColumnTable.tsx | 3 +- .../container/ListAlertRules/ListAlert.tsx | 99 +++++++++++++++---- .../src/container/ListAlertRules/styles.ts | 10 +- .../src/container/ListAlertRules/utils.ts | 25 +++++ .../src/hooks/ResizeTable/useSortableTable.ts | 44 +++++++++ 5 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 frontend/src/container/ListAlertRules/utils.ts create mode 100644 frontend/src/hooks/ResizeTable/useSortableTable.ts diff --git a/frontend/src/components/ResizeTable/DynamicColumnTable.tsx b/frontend/src/components/ResizeTable/DynamicColumnTable.tsx index 385734f11d..55af931d5c 100644 --- a/frontend/src/components/ResizeTable/DynamicColumnTable.tsx +++ b/frontend/src/components/ResizeTable/DynamicColumnTable.tsx @@ -27,6 +27,7 @@ function DynamicColumnTable({ ); useEffect(() => { + setColumnsData(columns); const visibleColumns = getVisibleColumns({ tablesource, columnsData: columns, @@ -42,7 +43,7 @@ function DynamicColumnTable({ : undefined, ); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [columns]); const onToggleHandler = (index: number) => ( checked: boolean, diff --git a/frontend/src/container/ListAlertRules/ListAlert.tsx b/frontend/src/container/ListAlertRules/ListAlert.tsx index f3fcecd6b5..52d54146b8 100644 --- a/frontend/src/container/ListAlertRules/ListAlert.tsx +++ b/frontend/src/container/ListAlertRules/ListAlert.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/display-name */ import { PlusOutlined } from '@ant-design/icons'; -import { Typography } from 'antd'; -import { ColumnsType } from 'antd/lib/table'; +import { Input, Typography } from 'antd'; +import type { ColumnsType } from 'antd/es/table/interface'; import saveAlertApi from 'api/alerts/save'; import DropDown from 'components/DropDown/DropDown'; import { @@ -14,9 +14,12 @@ import LabelColumn from 'components/TableRenderer/LabelColumn'; import TextToolTip from 'components/TextToolTip'; import { QueryParams } from 'constants/query'; import ROUTES from 'constants/routes'; +import useSortableTable from 'hooks/ResizeTable/useSortableTable'; import useComponentPermission from 'hooks/useComponentPermission'; +import useDebouncedFn from 'hooks/useDebouncedFunction'; import useInterval from 'hooks/useInterval'; import { useNotifications } from 'hooks/useNotifications'; +import useUrlQuery from 'hooks/useUrlQuery'; import history from 'lib/history'; import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi'; import { useCallback, useState } from 'react'; @@ -29,12 +32,19 @@ import { GettableAlert } from 'types/api/alerts/get'; import AppReducer from 'types/reducer/app'; import DeleteAlert from './DeleteAlert'; -import { Button, ButtonContainer, ColumnButton } from './styles'; +import { + Button, + ButtonContainer, + ColumnButton, + SearchContainer, +} from './styles'; import Status from './TableComponents/Status'; import ToggleAlertState from './ToggleAlertState'; +import { filterAlerts } from './utils'; + +const { Search } = Input; function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { - const [data, setData] = useState(allAlertRules || []); const { t } = useTranslation('common'); const { role, featureResponse } = useSelector( (state) => state.app, @@ -44,13 +54,39 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { role, ); + const params = useUrlQuery(); + const orderColumnParam = params.get('columnKey'); + const orderQueryParam = params.get('order'); + const paginationParam = params.get('page'); + const searchParams = params.get('search'); + const [searchString, setSearchString] = useState(searchParams || ''); + const [data, setData] = useState(() => { + const value = searchString.toLowerCase(); + const filteredData = filterAlerts(allAlertRules, value); + return filteredData || []; + }); + + // Type asuring + const sortingOrder: 'ascend' | 'descend' | null = + orderQueryParam === 'ascend' || orderQueryParam === 'descend' + ? orderQueryParam + : null; + + const { sortedInfo, handleChange } = useSortableTable( + sortingOrder, + orderColumnParam || '', + searchString, + ); + const { notifications: notificationsApi } = useNotifications(); useInterval(() => { (async (): Promise => { const { data: refetchData, status } = await refetch(); if (status === 'success') { - setData(refetchData?.payload || []); + const value = searchString.toLowerCase(); + const filteredData = filterAlerts(refetchData.payload || [], value); + setData(filteredData || []); } if (status === 'error') { notificationsApi.error({ @@ -128,6 +164,13 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { } }; + const handleSearch = useDebouncedFn((e: unknown) => { + const value = (e as React.BaseSyntheticEvent).target.value.toLowerCase(); + setSearchString(value); + const filteredData = filterAlerts(allAlertRules, value); + setData(filteredData); + }); + const dynamicColumns: ColumnsType = [ { title: 'Created At', @@ -142,6 +185,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { return prev - next; }, render: DateComponent, + sortOrder: + sortedInfo.columnKey === DynamicColumnsKey.CreatedAt + ? sortedInfo.order + : null, }, { title: 'Created By', @@ -163,6 +210,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { return prev - next; }, render: DateComponent, + sortOrder: + sortedInfo.columnKey === DynamicColumnsKey.UpdatedAt + ? sortedInfo.order + : null, }, { title: 'Updated By', @@ -183,6 +234,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { (b.state ? b.state.charCodeAt(0) : 1000) - (a.state ? a.state.charCodeAt(0) : 1000), render: (value): JSX.Element => , + sortOrder: sortedInfo.columnKey === 'state' ? sortedInfo.order : null, }, { title: 'Alert Name', @@ -198,6 +250,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { render: (value, record): JSX.Element => ( {value} ), + sortOrder: sortedInfo.columnKey === 'name' ? sortedInfo.order : null, }, { title: 'Severity', @@ -214,6 +267,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { return {severityValue}; }, + sortOrder: sortedInfo.columnKey === 'severity' ? sortedInfo.order : null, }, { title: 'Labels', @@ -271,26 +325,37 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { return ( <> - - + + + - {addNewAlert && ( - - )} - + {addNewAlert && ( + + )} + + ); diff --git a/frontend/src/container/ListAlertRules/styles.ts b/frontend/src/container/ListAlertRules/styles.ts index f2d0937950..c0deac35de 100644 --- a/frontend/src/container/ListAlertRules/styles.ts +++ b/frontend/src/container/ListAlertRules/styles.ts @@ -1,11 +1,17 @@ import { Button as ButtonComponent } from 'antd'; import styled from 'styled-components'; +export const SearchContainer = styled.div` + &&& { + display: flex; + margin-bottom: 2rem; + align-items: center; + gap: 2rem; + } +`; export const ButtonContainer = styled.div` &&& { display: flex; - justify-content: flex-end; - margin-bottom: 2rem; align-items: center; } `; diff --git a/frontend/src/container/ListAlertRules/utils.ts b/frontend/src/container/ListAlertRules/utils.ts new file mode 100644 index 0000000000..a1cef5add7 --- /dev/null +++ b/frontend/src/container/ListAlertRules/utils.ts @@ -0,0 +1,25 @@ +import { GettableAlert } from 'types/api/alerts/get'; + +export const filterAlerts = ( + allAlertRules: GettableAlert[], + filter: string, +): GettableAlert[] => { + const value = filter.toLowerCase(); + return allAlertRules.filter((alert) => { + const alertName = alert.alert.toLowerCase(); + const severity = alert.labels?.severity.toLowerCase(); + const labels = Object.keys(alert.labels || {}) + .filter((e) => e !== 'severity') + .join(' ') + .toLowerCase(); + + const labelValue = Object.values(alert.labels || {}); + + return ( + alertName.includes(value) || + severity?.includes(value) || + labels.includes(value) || + labelValue.includes(value) + ); + }); +}; diff --git a/frontend/src/hooks/ResizeTable/useSortableTable.ts b/frontend/src/hooks/ResizeTable/useSortableTable.ts new file mode 100644 index 0000000000..5425445578 --- /dev/null +++ b/frontend/src/hooks/ResizeTable/useSortableTable.ts @@ -0,0 +1,44 @@ +import { TableProps } from 'antd'; +import { SorterResult } from 'antd/es/table/interface'; +import { useEffect, useState } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; + +const useSortableTable = ( + initialOrder: 'ascend' | 'descend' | null, + initialColumnKey: string, + searchString: string, +): { + sortedInfo: SorterResult; + handleChange: TableProps['onChange']; +} => { + const history = useHistory(); + const { search } = useLocation(); + + useEffect(() => { + const searchParams = new URLSearchParams(search); + searchParams.set('search', searchString); + history.replace({ search: searchParams.toString() }); + }, [history, search, searchString]); + + const [sortedInfo, setSortedInfo] = useState>({ + order: initialOrder, + columnKey: initialColumnKey, + }); + + const handleChange: TableProps['onChange'] = (pagination, __, sorter) => { + if (Array.isArray(sorter)) return; + const searchParams = new URLSearchParams(search); + setSortedInfo(sorter as SorterResult); + searchParams.set('columnKey', sorter.columnKey as string); + searchParams.set('order', sorter.order as string); + searchParams.set( + 'page', + pagination.current ? pagination.current.toString() : '1', + ); + history.replace({ search: searchParams.toString() }); + }; + + return { sortedInfo, handleChange }; +}; + +export default useSortableTable;