From 31d3572dad3923aa8644abfc304031b84601cbb9 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 8 Jun 2022 15:57:31 +0000 Subject: [PATCH 1/2] chore(deps): update dependency msw to v0.42.1 --- frontend/package.json | 2 +- frontend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 292c3c87f8..b97a5c574c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -76,7 +76,7 @@ "immer": "9.0.14", "jsdom": "^19.0.0", "lodash.clonedeep": "4.5.0", - "msw": "0.42.0", + "msw": "0.42.1", "pkginfo": "^0.4.1", "plausible-tracker": "0.3.8", "prettier": "2.6.2", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e8229746e1..6f1303242f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4715,10 +4715,10 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msw@0.42.0: - version "0.42.0" - resolved "https://registry.yarnpkg.com/msw/-/msw-0.42.0.tgz#2286aefad82808888914e2bc5e40666e82b2824b" - integrity sha512-vB9rzgiGHoQGfkKpp3QZHxobzfuuQOJk+0bff0wtbK8k3P3CaUSt8bCwvExours682AY4mUfTjIkCsxy0JoS3w== +msw@0.42.1: + version "0.42.1" + resolved "https://registry.yarnpkg.com/msw/-/msw-0.42.1.tgz#2496d3e191754b68686e2530de459a2e102f85c4" + integrity sha512-LZZuz7VddL45gCBgfBWHyXj6a4W7OTJY0mZPoipJ3P/xwbuJwrtwB3IJrWlqBM8aink/eTKlRxwzmtIAwCj5yQ== dependencies: "@mswjs/cookies" "^0.2.0" "@mswjs/interceptors" "^0.16.3" From 608171ce9352e02ddb07c8ff89b828c89961698f Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Thu, 9 Jun 2022 11:34:55 +0200 Subject: [PATCH 2/2] Improve table performance (#1061) * improve table performance * revert to react-router navigation --- .../FeatureToggleListTable.tsx | 43 ++++++------ .../ColumnsMenu/ColumnsMenu.tsx | 17 +---- .../ProjectFeatureToggles.tsx | 37 ++++------- frontend/src/hooks/useLocalStorage.test.ts | 65 ------------------- frontend/src/hooks/useLocalStorage.ts | 34 ---------- frontend/src/utils/createLocalStorage.ts | 30 +++++++++ 6 files changed, 68 insertions(+), 158 deletions(-) delete mode 100644 frontend/src/hooks/useLocalStorage.test.ts delete mode 100644 frontend/src/hooks/useLocalStorage.ts create mode 100644 frontend/src/utils/createLocalStorage.ts diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx index d3ca563b0a..578d4f1b63 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx @@ -22,7 +22,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { sortTypes } from 'utils/sortTypes'; -import { useLocalStorage } from 'hooks/useLocalStorage'; +import { createLocalStorage } from 'utils/createLocalStorage'; import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; import { FeatureSchema } from 'openapi'; import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton'; @@ -99,21 +99,32 @@ const columns = [ const defaultSort: SortingRule = { id: 'createdAt', desc: true }; +const { value: storedParams, setValue: setStoredParams } = createLocalStorage( + 'FeatureToggleListTable:v1', + defaultSort +); + export const FeatureToggleListTable: VFC = () => { const theme = useTheme(); const rowHeight = theme.shape.tableRowHeight; const { classes } = useStyles(); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); - const [searchParams, setSearchParams] = useSearchParams(); - const [storedParams, setStoredParams] = useLocalStorage( - 'FeatureToggleListTable:v1', - defaultSort - ); const { features = [], loading } = useFeatures(); - const [searchValue, setSearchValue] = useState( - searchParams.get('search') || '' - ); + const [searchParams, setSearchParams] = useSearchParams(); + const [initialState] = useState(() => ({ + sortBy: [ + { + id: searchParams.get('sort') || storedParams.id, + desc: searchParams.has('order') + ? searchParams.get('order') === 'desc' + : storedParams.desc, + }, + ], + hiddenColumns: ['description'], + globalFilter: searchParams.get('search') || '', + })); + const [searchValue, setSearchValue] = useState(initialState.globalFilter); const { data: searchedData, @@ -129,18 +140,6 @@ export const FeatureToggleListTable: VFC = () => { [searchedData, loading] ); - const [initialState] = useState(() => ({ - sortBy: [ - { - id: searchParams.get('sort') || storedParams.id, - desc: searchParams.has('order') - ? searchParams.get('order') === 'desc' - : storedParams.desc, - }, - ], - hiddenColumns: ['description'], - })); - const { getTableProps, getTableBodyProps, @@ -188,7 +187,7 @@ export const FeatureToggleListTable: VFC = () => { replace: true, }); setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false }); - }, [sortBy, searchValue, setSearchParams, setStoredParams]); + }, [sortBy, searchValue, setSearchParams]); const [firstRenderedIndex, lastRenderedIndex] = useVirtualizedRange(rowHeight); diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ColumnsMenu/ColumnsMenu.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ColumnsMenu/ColumnsMenu.tsx index b841def709..acebce146e 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ColumnsMenu/ColumnsMenu.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ColumnsMenu/ColumnsMenu.tsx @@ -30,7 +30,6 @@ interface IColumnsMenuProps { dividerBefore?: string[]; dividerAfter?: string[]; isCustomized?: boolean; - onCustomize?: (columns: string[]) => void; setHiddenColumns: ( hiddenColumns: | string[] @@ -44,7 +43,6 @@ export const ColumnsMenu: VFC = ({ dividerBefore = [], dividerAfter = [], isCustomized = false, - onCustomize = () => {}, setHiddenColumns, }) => { const [anchorEl, setAnchorEl] = useState(null); @@ -96,17 +94,6 @@ export const ColumnsMenu: VFC = ({ setAnchorEl(null); }; - const onItemClick = (column: typeof allColumns[number]) => { - onCustomize([ - ...allColumns - .filter(({ isVisible }) => isVisible) - .map(({ id }) => id) - .filter(id => !staticColumns.includes(id) && id !== column.id), - ...(!column.isVisible ? [column.id] : []), - ]); - column.toggleHidden(column.isVisible); - }; - const isOpen = Boolean(anchorEl); const id = `columns-menu`; const menuId = `columns-menu-list-${id}`; @@ -162,7 +149,9 @@ export const ColumnsMenu: VFC = ({ show={} />, onItemClick(column)} + onClick={() => + column.toggleHidden(column.isVisible) + } disabled={staticColumns.includes(column.id)} className={classes.menuItem} > diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx index 0d4a7af02e..d24c654b59 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -29,7 +29,7 @@ import { } from 'component/common/Table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import useProject from 'hooks/api/getters/useProject/useProject'; -import { useLocalStorage } from 'hooks/useLocalStorage'; +import { createLocalStorage } from 'utils/createLocalStorage'; import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; import useToast from 'hooks/useToast'; import { ENVIRONMENT_STRATEGY_ERROR } from 'constants/apiErrors'; @@ -93,7 +93,14 @@ export const ProjectFeatureToggles = ({ string | undefined >(); const projectId = useRequiredPathParam('projectId'); + + const { value: storedParams, setValue: setStoredParams } = + createLocalStorage( + `${projectId}:FeatureToggleListTable:v1`, + defaultSort + ); const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); const { uiConfig } = useUiConfig(); const environments = useEnvironmentsRef( loading ? ['a', 'b', 'c'] : newEnvironments @@ -235,12 +242,6 @@ export const ProjectFeatureToggles = ({ [projectId, environments, onToggle, loading] ); - const [searchParams, setSearchParams] = useSearchParams(); - const [storedParams, setStoredParams] = useLocalStorage( - `${projectId}:ProjectFeatureToggles`, - defaultSort - ); - const [searchValue, setSearchValue] = useState( searchParams.get('search') || '' ); @@ -300,6 +301,7 @@ export const ProjectFeatureToggles = ({ const initialState = useMemo( () => { + const searchParams = new URLSearchParams(); const allColumnIds = columns.map( (column: any) => column?.accessor || column?.id ); @@ -384,27 +386,17 @@ export const ProjectFeatureToggles = ({ setSearchParams(tableState, { replace: true, }); - setStoredParams({ + setStoredParams(params => ({ + ...params, id: sortBy[0].id, desc: sortBy[0].desc || false, columns: tableState.columns.split(','), - }); + })); // eslint-disable-next-line react-hooks/exhaustive-deps }, [loading, sortBy, hiddenColumns, searchValue, setSearchParams]); - const onCustomizeColumns = useCallback( - visibleColumns => { - setStoredParams(storedParams => ({ - ...storedParams, - columns: visibleColumns, - })); - }, - [setStoredParams] - ); - const [firstRenderedIndex, lastRenderedIndex] = useVirtualizedRange( - rowHeight, - 20 - ); + const [firstRenderedIndex, lastRenderedIndex] = + useVirtualizedRange(rowHeight); return ( diff --git a/frontend/src/hooks/useLocalStorage.test.ts b/frontend/src/hooks/useLocalStorage.test.ts deleted file mode 100644 index c9a237eb82..0000000000 --- a/frontend/src/hooks/useLocalStorage.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { vi } from 'vitest'; -import { useLocalStorage } from './useLocalStorage'; -import { act, renderHook } from '@testing-library/react-hooks'; - -describe('useLocalStorage', () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('should return an object with data and mutate properties', () => { - vi.spyOn(Storage.prototype, 'getItem').mockImplementationOnce(() => - JSON.stringify(undefined) - ); - const { result } = renderHook(() => useLocalStorage('key', {})); - - expect(result.current).toEqual([{}, expect.any(Function)]); - }); - - it('returns default value', () => { - vi.spyOn(Storage.prototype, 'getItem').mockImplementationOnce(() => - JSON.stringify(undefined) - ); - const { result } = renderHook(() => - useLocalStorage('key', { key: 'value' }) - ); - - expect(result.current).toEqual([ - { key: 'value' }, - expect.any(Function), - ]); - }); - - it('returns a value from local storage', async () => { - vi.spyOn(Storage.prototype, 'getItem').mockImplementationOnce(() => - JSON.stringify({ key: 'value' }) - ); - - const { result, waitFor } = renderHook(() => - useLocalStorage('test-key', {}) - ); - - await waitFor(() => - expect(result.current).toEqual([ - { key: 'value' }, - expect.any(Function), - ]) - ); - }); - - it('sets new value to local storage', async () => { - const setItem = vi.spyOn(Storage.prototype, 'setItem'); - const { result } = renderHook(() => - useLocalStorage('test-key', { key: 'initial-value' }) - ); - - await act(async () => { - result.current[1]({ key: 'new-value' }); - }); - - expect(setItem).toHaveBeenCalledWith( - ':test-key:useLocalStorage:v1', - JSON.stringify({ key: 'new-value' }) - ); - }); -}); diff --git a/frontend/src/hooks/useLocalStorage.ts b/frontend/src/hooks/useLocalStorage.ts deleted file mode 100644 index 409279e337..0000000000 --- a/frontend/src/hooks/useLocalStorage.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Dispatch, SetStateAction, useCallback, useState } from 'react'; -import { basePath } from 'utils/formatPath'; -import { getLocalStorageItem, setLocalStorageItem } from '../utils/storage'; - -export const useLocalStorage = ( - key: string, - initialValue: T -) => { - const internalKey = `${basePath}:${key}:useLocalStorage:v1`; - const [value, setValue] = useState(() => { - const state = getLocalStorageItem(internalKey); - if (state === undefined) { - return initialValue; - } - return state; - }); - - const onUpdate = useCallback>>( - value => { - if (value instanceof Function) { - setValue(prev => { - const output = value(prev); - setLocalStorageItem(internalKey, output); - return output; - }); - } - setLocalStorageItem(internalKey, value); - setValue(value); - }, - [internalKey] - ); - - return [value, onUpdate] as const; -}; diff --git a/frontend/src/utils/createLocalStorage.ts b/frontend/src/utils/createLocalStorage.ts new file mode 100644 index 0000000000..50742fdcb2 --- /dev/null +++ b/frontend/src/utils/createLocalStorage.ts @@ -0,0 +1,30 @@ +import { basePath } from './formatPath'; +import { getLocalStorageItem, setLocalStorageItem } from './storage'; + +export const createLocalStorage = ( + key: string, + initialValue: T +) => { + const internalKey = `${basePath}:${key}:localStorage:v2`; + const value = (() => { + const state = getLocalStorageItem(internalKey); + if (state === undefined) { + return initialValue; + } + return state; + })(); + + const onUpdate = (newValue: T | ((prev: T) => T)): T => { + if (newValue instanceof Function) { + const previousValue = getLocalStorageItem(internalKey); + const output = newValue(previousValue ?? initialValue); + setLocalStorageItem(internalKey, output); + return output; + } + + setLocalStorageItem(internalKey, newValue); + return newValue; + }; + + return { value, setValue: onUpdate }; +};