1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

Merge remote-tracking branch 'origin/archive_table' into archive_table

This commit is contained in:
andreas-unleash 2022-06-09 12:47:22 +03:00
commit efa66b2ab2
8 changed files with 73 additions and 163 deletions

View File

@ -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",

View File

@ -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';
@ -101,21 +101,32 @@ const columns = [
const defaultSort: SortingRule<string> = { 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,
@ -131,18 +142,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,
@ -190,7 +189,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);

View File

@ -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<IColumnsMenuProps> = ({
dividerBefore = [],
dividerAfter = [],
isCustomized = false,
onCustomize = () => {},
setHiddenColumns,
}) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
@ -96,17 +94,6 @@ export const ColumnsMenu: VFC<IColumnsMenuProps> = ({
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<IColumnsMenuProps> = ({
show={<Divider className={classes.divider} />}
/>,
<MenuItem
onClick={() => onItemClick(column)}
onClick={() =>
column.toggleHidden(column.isVisible)
}
disabled={staticColumns.includes(column.id)}
className={classes.menuItem}
>

View File

@ -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 (
<PageContent
@ -436,7 +428,6 @@ export const ProjectFeatureToggles = ({
dividerAfter={['createdAt']}
dividerBefore={['Actions']}
isCustomized={Boolean(storedParams.columns)}
onCustomize={onCustomizeColumns}
setHiddenColumns={setHiddenColumns}
/>
<PageHeader.Divider sx={{ marginLeft: 0 }} />

View File

@ -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' })
);
});
});

View File

@ -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 = <T extends object>(
key: string,
initialValue: T
) => {
const internalKey = `${basePath}:${key}:useLocalStorage:v1`;
const [value, setValue] = useState<T>(() => {
const state = getLocalStorageItem<T>(internalKey);
if (state === undefined) {
return initialValue;
}
return state;
});
const onUpdate = useCallback<Dispatch<SetStateAction<T>>>(
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;
};

View File

@ -0,0 +1,30 @@
import { basePath } from './formatPath';
import { getLocalStorageItem, setLocalStorageItem } from './storage';
export const createLocalStorage = <T extends object>(
key: string,
initialValue: T
) => {
const internalKey = `${basePath}:${key}:localStorage:v2`;
const value = (() => {
const state = getLocalStorageItem<T>(internalKey);
if (state === undefined) {
return initialValue;
}
return state;
})();
const onUpdate = (newValue: T | ((prev: T) => T)): T => {
if (newValue instanceof Function) {
const previousValue = getLocalStorageItem<T>(internalKey);
const output = newValue(previousValue ?? initialValue);
setLocalStorageItem(internalKey, output);
return output;
}
setLocalStorageItem(internalKey, newValue);
return newValue;
};
return { value, setValue: onUpdate };
};

View File

@ -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"