mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-09 01:17:06 +02:00
feat: reset persistent table offset on change (#5650)
This commit is contained in:
parent
53b32db278
commit
0726887bb8
@ -92,9 +92,6 @@ export const FeatureToggleListTable: VFC = () => {
|
|||||||
'features-list-table',
|
'features-list-table',
|
||||||
stateConfig,
|
stateConfig,
|
||||||
);
|
);
|
||||||
// offset needs to be first so we can override it
|
|
||||||
const setTableStateWithOffsetReset: typeof setTableState = (data) =>
|
|
||||||
setTableState({ offset: 0, ...data });
|
|
||||||
|
|
||||||
const filterState = {
|
const filterState = {
|
||||||
project: tableState.project,
|
project: tableState.project,
|
||||||
@ -141,7 +138,7 @@ export const FeatureToggleListTable: VFC = () => {
|
|||||||
<FavoriteIconHeader
|
<FavoriteIconHeader
|
||||||
isActive={tableState.favoritesFirst}
|
isActive={tableState.favoritesFirst}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setTableStateWithOffsetReset({
|
setTableState({
|
||||||
favoritesFirst: !tableState.favoritesFirst,
|
favoritesFirst: !tableState.favoritesFirst,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -230,7 +227,7 @@ export const FeatureToggleListTable: VFC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const table = useReactTable(
|
const table = useReactTable(
|
||||||
withTableState(tableState, setTableStateWithOffsetReset, {
|
withTableState(tableState, setTableState, {
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
}),
|
}),
|
||||||
@ -255,8 +252,7 @@ export const FeatureToggleListTable: VFC = () => {
|
|||||||
}
|
}
|
||||||
}, [isSmallScreen, isMediumScreen]);
|
}, [isSmallScreen, isMediumScreen]);
|
||||||
|
|
||||||
const setSearchValue = (query = '') =>
|
const setSearchValue = (query = '') => setTableState({ query });
|
||||||
setTableStateWithOffsetReset({ query });
|
|
||||||
|
|
||||||
const rows = table.getRowModel().rows;
|
const rows = table.getRowModel().rows;
|
||||||
|
|
||||||
@ -340,7 +336,7 @@ export const FeatureToggleListTable: VFC = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FeatureToggleFilters
|
<FeatureToggleFilters
|
||||||
onChange={setTableStateWithOffsetReset}
|
onChange={setTableState}
|
||||||
state={filterState}
|
state={filterState}
|
||||||
/>
|
/>
|
||||||
<SearchHighlightProvider value={tableState.query || ''}>
|
<SearchHighlightProvider value={tableState.query || ''}>
|
||||||
|
@ -33,7 +33,7 @@ interface IAddFilterButtonProps {
|
|||||||
availableFilters: IFilterItem[];
|
availableFilters: IFilterItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddFilterButton = ({
|
export const AddFilterButton = ({
|
||||||
visibleOptions,
|
visibleOptions,
|
||||||
setVisibleOptions,
|
setVisibleOptions,
|
||||||
hiddenOptions,
|
hiddenOptions,
|
||||||
@ -87,5 +87,3 @@ const AddFilterButton = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddFilterButton;
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState, VFC } from 'react';
|
import { useEffect, useState, VFC } from 'react';
|
||||||
import { Box, styled } from '@mui/material';
|
import { Box, styled } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import AddFilterButton from '../AddFilterButton';
|
import { AddFilterButton } from '../AddFilterButton';
|
||||||
import { FilterDateItem } from 'component/common/FilterDateItem/FilterDateItem';
|
import { FilterDateItem } from 'component/common/FilterDateItem/FilterDateItem';
|
||||||
import { FilterItem, FilterItemParams } from '../FilterItem/FilterItem';
|
import { FilterItem, FilterItemParams } from '../FilterItem/FilterItem';
|
||||||
|
|
||||||
|
@ -1,68 +1,26 @@
|
|||||||
import React, {
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
type CSSProperties,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import {
|
|
||||||
Checkbox,
|
|
||||||
IconButton,
|
|
||||||
styled,
|
|
||||||
Tooltip,
|
|
||||||
useMediaQuery,
|
|
||||||
Box,
|
|
||||||
useTheme,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { Add } from '@mui/icons-material';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import {
|
|
||||||
useFlexLayout,
|
|
||||||
usePagination,
|
|
||||||
useRowSelect,
|
|
||||||
useSortBy,
|
|
||||||
useTable,
|
|
||||||
} from 'react-table';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
|
||||||
import { getCreateTogglePath } from 'utils/routePathHelpers';
|
|
||||||
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
||||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
|
||||||
import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell';
|
|
||||||
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
|
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
|
||||||
import { IProject } from 'interfaces/project';
|
import { IProject } from 'interfaces/project';
|
||||||
import { PaginatedTable, VirtualizedTable } from 'component/common/Table';
|
import { PaginatedTable } from 'component/common/Table';
|
||||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog';
|
|
||||||
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
|
|
||||||
import { getColumnValues, includesFilter, useSearch } from 'hooks/useSearch';
|
|
||||||
import { Search } from 'component/common/Search/Search';
|
|
||||||
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
|
||||||
import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader';
|
import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader';
|
||||||
import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell';
|
import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell';
|
||||||
import { ProjectEnvironmentType } from '../../ProjectFeatureToggles/hooks/useEnvironmentsRef';
|
import { ProjectEnvironmentType } from '../../ProjectFeatureToggles/hooks/useEnvironmentsRef';
|
||||||
import { ActionsCell } from '../../ProjectFeatureToggles/ActionsCell/ActionsCell';
|
import { ActionsCell } from '../../ProjectFeatureToggles/ActionsCell/ActionsCell';
|
||||||
import { ExperimentalColumnsMenu as ColumnsMenu } from './ExperimentalColumnsMenu/ExperimentalColumnsMenu';
|
import { ExperimentalColumnsMenu as ColumnsMenu } from './ExperimentalColumnsMenu/ExperimentalColumnsMenu';
|
||||||
import { useStyles } from '../../ProjectFeatureToggles/ProjectFeatureToggles.styles';
|
|
||||||
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
|
import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
|
||||||
import { FeatureTagCell } from 'component/common/Table/cells/FeatureTagCell/FeatureTagCell';
|
|
||||||
import FileDownload from '@mui/icons-material/FileDownload';
|
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog';
|
import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog';
|
||||||
import { MemoizedRowSelectCell } from '../../ProjectFeatureToggles/RowSelectCell/RowSelectCell';
|
import { MemoizedRowSelectCell } from '../../ProjectFeatureToggles/RowSelectCell/RowSelectCell';
|
||||||
import { BatchSelectionActionsBar } from 'component/common/BatchSelectionActionsBar/BatchSelectionActionsBar';
|
import { BatchSelectionActionsBar } from 'component/common/BatchSelectionActionsBar/BatchSelectionActionsBar';
|
||||||
import { ProjectFeaturesBatchActions } from '../../ProjectFeatureToggles/ProjectFeaturesBatchActions/ProjectFeaturesBatchActions';
|
import { ProjectFeaturesBatchActions } from '../../ProjectFeatureToggles/ProjectFeaturesBatchActions/ProjectFeaturesBatchActions';
|
||||||
import { MemoizedFeatureEnvironmentSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell';
|
import { MemoizedFeatureEnvironmentSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell';
|
||||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
import { ListItemType } from '../../ProjectFeatureToggles/ProjectFeatureToggles.types';
|
|
||||||
import { createFeatureToggleCell } from '../../ProjectFeatureToggles/FeatureToggleSwitch/createFeatureToggleCell';
|
|
||||||
import { useFeatureToggleSwitch } from '../../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch';
|
import { useFeatureToggleSwitch } from '../../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch';
|
||||||
import useLoading from 'hooks/useLoading';
|
import useLoading from 'hooks/useLoading';
|
||||||
import { StickyPaginationBar } from '../../../../common/Table/StickyPaginationBar/StickyPaginationBar';
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_PAGE_LIMIT,
|
DEFAULT_PAGE_LIMIT,
|
||||||
useFeatureSearch,
|
useFeatureSearch,
|
||||||
@ -74,11 +32,11 @@ import {
|
|||||||
FilterItemParam,
|
FilterItemParam,
|
||||||
} from 'utils/serializeQueryParams';
|
} from 'utils/serializeQueryParams';
|
||||||
import {
|
import {
|
||||||
|
ArrayParam,
|
||||||
|
encodeQueryParams,
|
||||||
NumberParam,
|
NumberParam,
|
||||||
StringParam,
|
StringParam,
|
||||||
ArrayParam,
|
|
||||||
withDefault,
|
withDefault,
|
||||||
encodeQueryParams,
|
|
||||||
} from 'use-query-params';
|
} from 'use-query-params';
|
||||||
import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader';
|
import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader';
|
||||||
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
|
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
|
||||||
|
@ -172,11 +172,61 @@ describe('usePersistentTableState', () => {
|
|||||||
screen.getByText('Update Offset').click();
|
screen.getByText('Update Offset').click();
|
||||||
screen.getByText('Update State').click();
|
screen.getByText('Update State').click();
|
||||||
|
|
||||||
expect(window.location.href).toContain('my-url?query=after&offset=20');
|
expect(window.location.href).toContain('my-url?query=after&offset=0');
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const { value } = createLocalStorage('testKey', {});
|
const { value } = createLocalStorage('testKey', {});
|
||||||
expect(value).toStrictEqual({ query: 'after' });
|
expect(value).toStrictEqual({ query: 'after' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('resets offset to 0 on state update', async () => {
|
||||||
|
createLocalStorage('testKey', {}).setValue({ query: 'before' });
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TestComponent
|
||||||
|
keyName='testKey'
|
||||||
|
queryParamsDefinition={{
|
||||||
|
query: StringParam,
|
||||||
|
offset: NumberParam,
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
{ route: '/my-url?query=before&offset=10' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(window.location.href).toContain('my-url?query=before&offset=10');
|
||||||
|
|
||||||
|
screen.getByText('Update State').click();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(window.location.href).toContain(
|
||||||
|
'my-url?query=after&offset=0',
|
||||||
|
);
|
||||||
|
expect(window.location.href).not.toContain('offset=10');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not reset offset to 0 without offset decoder', async () => {
|
||||||
|
createLocalStorage('testKey', {}).setValue({ query: 'before' });
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TestComponent
|
||||||
|
keyName='testKey'
|
||||||
|
queryParamsDefinition={{
|
||||||
|
query: StringParam,
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
{ route: '/my-url?query=before&offset=10' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(window.location.href).toContain('my-url?query=before&offset=10');
|
||||||
|
|
||||||
|
screen.getByText('Update State').click();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(window.location.href).toContain(
|
||||||
|
'my-url?query=after&offset=10',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useCallback } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { createLocalStorage } from 'utils/createLocalStorage';
|
import { createLocalStorage } from 'utils/createLocalStorage';
|
||||||
import { useQueryParams, encodeQueryParams } from 'use-query-params';
|
import { encodeQueryParams, useQueryParams } from 'use-query-params';
|
||||||
import { QueryParamConfigMap } from 'serialize-query-params/src/types';
|
import { QueryParamConfigMap } from 'serialize-query-params/src/types';
|
||||||
|
|
||||||
const usePersistentSearchParams = <T extends QueryParamConfigMap>(
|
const usePersistentSearchParams = <T extends QueryParamConfigMap>(
|
||||||
@ -39,7 +39,41 @@ export const usePersistentTableState = <T extends QueryParamConfigMap>(
|
|||||||
queryParamsDefinition,
|
queryParamsDefinition,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tableState, setTableState] = useQueryParams(queryParamsDefinition);
|
const [tableState, setTableStateInternal] = useQueryParams(
|
||||||
|
queryParamsDefinition,
|
||||||
|
);
|
||||||
|
|
||||||
|
type SetTableStateInternalParam = Parameters<
|
||||||
|
typeof setTableStateInternal
|
||||||
|
>[0];
|
||||||
|
|
||||||
|
const setTableState = useCallback(
|
||||||
|
(newState: SetTableStateInternalParam) => {
|
||||||
|
if (!queryParamsDefinition.offset) {
|
||||||
|
return setTableStateInternal(newState);
|
||||||
|
}
|
||||||
|
if (typeof newState === 'function') {
|
||||||
|
setTableStateInternal((prevState) => {
|
||||||
|
const updatedState = (newState as Function)(prevState);
|
||||||
|
return queryParamsDefinition.offset
|
||||||
|
? {
|
||||||
|
offset: queryParamsDefinition.offset.decode('0'),
|
||||||
|
...updatedState,
|
||||||
|
}
|
||||||
|
: updatedState;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const updatedState = queryParamsDefinition.offset
|
||||||
|
? {
|
||||||
|
offset: queryParamsDefinition.offset.decode('0'),
|
||||||
|
...newState,
|
||||||
|
}
|
||||||
|
: newState;
|
||||||
|
setTableStateInternal(updatedState);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setTableStateInternal, queryParamsDefinition.offset],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { offset, ...rest } = tableState;
|
const { offset, ...rest } = tableState;
|
||||||
|
Loading…
Reference in New Issue
Block a user