mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 21:09:09 +08:00
[refactor]: persistance of sorting and page in table (#4221)
This commit is contained in:
parent
9d44ce3ee2
commit
77b4e71543
@ -27,6 +27,7 @@ function DynamicColumnTable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setColumnsData(columns);
|
||||||
const visibleColumns = getVisibleColumns({
|
const visibleColumns = getVisibleColumns({
|
||||||
tablesource,
|
tablesource,
|
||||||
columnsData: columns,
|
columnsData: columns,
|
||||||
@ -42,7 +43,7 @@ function DynamicColumnTable({
|
|||||||
: undefined,
|
: undefined,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, [columns]);
|
||||||
|
|
||||||
const onToggleHandler = (index: number) => (
|
const onToggleHandler = (index: number) => (
|
||||||
checked: boolean,
|
checked: boolean,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Typography } from 'antd';
|
import { Input, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import type { ColumnsType } from 'antd/es/table/interface';
|
||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import DropDown from 'components/DropDown/DropDown';
|
import DropDown from 'components/DropDown/DropDown';
|
||||||
import {
|
import {
|
||||||
@ -14,9 +14,12 @@ import LabelColumn from 'components/TableRenderer/LabelColumn';
|
|||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import useSortableTable from 'hooks/ResizeTable/useSortableTable';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
|
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||||
import useInterval from 'hooks/useInterval';
|
import useInterval from 'hooks/useInterval';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
@ -29,12 +32,19 @@ import { GettableAlert } from 'types/api/alerts/get';
|
|||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
import DeleteAlert from './DeleteAlert';
|
import DeleteAlert from './DeleteAlert';
|
||||||
import { Button, ButtonContainer, ColumnButton } from './styles';
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonContainer,
|
||||||
|
ColumnButton,
|
||||||
|
SearchContainer,
|
||||||
|
} from './styles';
|
||||||
import Status from './TableComponents/Status';
|
import Status from './TableComponents/Status';
|
||||||
import ToggleAlertState from './ToggleAlertState';
|
import ToggleAlertState from './ToggleAlertState';
|
||||||
|
import { filterAlerts } from './utils';
|
||||||
|
|
||||||
|
const { Search } = Input;
|
||||||
|
|
||||||
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||||
const [data, setData] = useState<GettableAlert[]>(allAlertRules || []);
|
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { role, featureResponse } = useSelector<AppState, AppReducer>(
|
const { role, featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
(state) => state.app,
|
(state) => state.app,
|
||||||
@ -44,13 +54,39 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
role,
|
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<string>(searchParams || '');
|
||||||
|
const [data, setData] = useState<GettableAlert[]>(() => {
|
||||||
|
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<GettableAlert>(
|
||||||
|
sortingOrder,
|
||||||
|
orderColumnParam || '',
|
||||||
|
searchString,
|
||||||
|
);
|
||||||
|
|
||||||
const { notifications: notificationsApi } = useNotifications();
|
const { notifications: notificationsApi } = useNotifications();
|
||||||
|
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
(async (): Promise<void> => {
|
(async (): Promise<void> => {
|
||||||
const { data: refetchData, status } = await refetch();
|
const { data: refetchData, status } = await refetch();
|
||||||
if (status === 'success') {
|
if (status === 'success') {
|
||||||
setData(refetchData?.payload || []);
|
const value = searchString.toLowerCase();
|
||||||
|
const filteredData = filterAlerts(refetchData.payload || [], value);
|
||||||
|
setData(filteredData || []);
|
||||||
}
|
}
|
||||||
if (status === 'error') {
|
if (status === 'error') {
|
||||||
notificationsApi.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<GettableAlert> = [
|
const dynamicColumns: ColumnsType<GettableAlert> = [
|
||||||
{
|
{
|
||||||
title: 'Created At',
|
title: 'Created At',
|
||||||
@ -142,6 +185,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
return prev - next;
|
return prev - next;
|
||||||
},
|
},
|
||||||
render: DateComponent,
|
render: DateComponent,
|
||||||
|
sortOrder:
|
||||||
|
sortedInfo.columnKey === DynamicColumnsKey.CreatedAt
|
||||||
|
? sortedInfo.order
|
||||||
|
: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Created By',
|
title: 'Created By',
|
||||||
@ -163,6 +210,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
return prev - next;
|
return prev - next;
|
||||||
},
|
},
|
||||||
render: DateComponent,
|
render: DateComponent,
|
||||||
|
sortOrder:
|
||||||
|
sortedInfo.columnKey === DynamicColumnsKey.UpdatedAt
|
||||||
|
? sortedInfo.order
|
||||||
|
: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Updated By',
|
title: 'Updated By',
|
||||||
@ -183,6 +234,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
(b.state ? b.state.charCodeAt(0) : 1000) -
|
(b.state ? b.state.charCodeAt(0) : 1000) -
|
||||||
(a.state ? a.state.charCodeAt(0) : 1000),
|
(a.state ? a.state.charCodeAt(0) : 1000),
|
||||||
render: (value): JSX.Element => <Status status={value} />,
|
render: (value): JSX.Element => <Status status={value} />,
|
||||||
|
sortOrder: sortedInfo.columnKey === 'state' ? sortedInfo.order : null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Alert Name',
|
title: 'Alert Name',
|
||||||
@ -198,6 +250,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
render: (value, record): JSX.Element => (
|
render: (value, record): JSX.Element => (
|
||||||
<Typography.Link onClick={onEditHandler(record)}>{value}</Typography.Link>
|
<Typography.Link onClick={onEditHandler(record)}>{value}</Typography.Link>
|
||||||
),
|
),
|
||||||
|
sortOrder: sortedInfo.columnKey === 'name' ? sortedInfo.order : null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Severity',
|
title: 'Severity',
|
||||||
@ -214,6 +267,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
|
|
||||||
return <Typography>{severityValue}</Typography>;
|
return <Typography>{severityValue}</Typography>;
|
||||||
},
|
},
|
||||||
|
sortOrder: sortedInfo.columnKey === 'severity' ? sortedInfo.order : null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Labels',
|
title: 'Labels',
|
||||||
@ -271,6 +325,12 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<SearchContainer>
|
||||||
|
<Search
|
||||||
|
placeholder="Search by Alert Name, Severity and Labels"
|
||||||
|
onChange={handleSearch}
|
||||||
|
defaultValue={searchString}
|
||||||
|
/>
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
<TextToolTip
|
<TextToolTip
|
||||||
{...{
|
{...{
|
||||||
@ -285,12 +345,17 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
|
</SearchContainer>
|
||||||
<DynamicColumnTable
|
<DynamicColumnTable
|
||||||
tablesource={TableDataSource.Alert}
|
tablesource={TableDataSource.Alert}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
dynamicColumns={dynamicColumns}
|
dynamicColumns={dynamicColumns}
|
||||||
|
onChange={handleChange}
|
||||||
|
pagination={{
|
||||||
|
defaultCurrent: Number(paginationParam) || 1,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import { Button as ButtonComponent } from 'antd';
|
import { Button as ButtonComponent } from 'antd';
|
||||||
import styled from 'styled-components';
|
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`
|
export const ButtonContainer = styled.div`
|
||||||
&&& {
|
&&& {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
25
frontend/src/container/ListAlertRules/utils.ts
Normal file
25
frontend/src/container/ListAlertRules/utils.ts
Normal file
@ -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)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
44
frontend/src/hooks/ResizeTable/useSortableTable.ts
Normal file
44
frontend/src/hooks/ResizeTable/useSortableTable.ts
Normal file
@ -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 = <T>(
|
||||||
|
initialOrder: 'ascend' | 'descend' | null,
|
||||||
|
initialColumnKey: string,
|
||||||
|
searchString: string,
|
||||||
|
): {
|
||||||
|
sortedInfo: SorterResult<T>;
|
||||||
|
handleChange: TableProps<T>['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<SorterResult<T>>({
|
||||||
|
order: initialOrder,
|
||||||
|
columnKey: initialColumnKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange: TableProps<T>['onChange'] = (pagination, __, sorter) => {
|
||||||
|
if (Array.isArray(sorter)) return;
|
||||||
|
const searchParams = new URLSearchParams(search);
|
||||||
|
setSortedInfo(sorter as SorterResult<T>);
|
||||||
|
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;
|
Loading…
x
Reference in New Issue
Block a user