1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-28 00:06:53 +01:00

PR comments and tidying up

This commit is contained in:
andreas-unleash 2022-06-08 13:26:41 +03:00
parent c26bfcf9da
commit 854ffaf0b2
4 changed files with 165 additions and 137 deletions

View File

@ -9,16 +9,11 @@ import {
TableRow, TableRow,
TableSearch, TableSearch,
} from 'component/common/Table'; } from 'component/common/Table';
import { import { useFlexLayout, useSortBy, useTable } from 'react-table';
useFlexLayout,
useGlobalFilter,
useSortBy,
useTable,
} from 'react-table';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { useMediaQuery } from '@mui/material'; import { useMediaQuery } from '@mui/material';
import { sortTypes } from 'utils/sortTypes'; import { sortTypes } from 'utils/sortTypes';
import { useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
@ -26,29 +21,26 @@ import { FeatureTypeCell } from '../../common/Table/cells/FeatureTypeCell/Featur
import { FeatureSeenCell } from '../../common/Table/cells/FeatureSeenCell/FeatureSeenCell'; import { FeatureSeenCell } from '../../common/Table/cells/FeatureSeenCell/FeatureSeenCell';
import { LinkCell } from '../../common/Table/cells/LinkCell/LinkCell'; import { LinkCell } from '../../common/Table/cells/LinkCell/LinkCell';
import { FeatureStaleCell } from '../../feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; import { FeatureStaleCell } from '../../feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell';
import { TimeAgoCell } from '../../common/Table/cells/TimeAgoCell/TimeAgoCell';
import { ReviveArchivedFeatureCell } from 'component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell'; import { ReviveArchivedFeatureCell } from 'component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell';
import { useStyles } from '../../feature/FeatureToggleList/styles'; import { useStyles } from '../../feature/FeatureToggleList/styles';
import { useVirtualizedRange } from '../../../hooks/useVirtualizedRange'; import { featuresPlaceholder } from '../../feature/FeatureToggleList/FeatureToggleListTable';
import {
featuresPlaceholder,
PageQueryType,
} from '../../feature/FeatureToggleList/FeatureToggleListTable';
import theme from 'themes/theme'; import theme from 'themes/theme';
import { FeatureSchema } from '../../../openapi'; import { FeatureSchema } from '../../../openapi';
import { useFeatureArchiveApi } from '../../../hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; import { useFeatureArchiveApi } from '../../../hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi';
import useToast from '../../../hooks/useToast'; import useToast from '../../../hooks/useToast';
import { formatUnknownError } from '../../../utils/formatUnknownError'; import { formatUnknownError } from '../../../utils/formatUnknownError';
import { useSearch } from '../../../hooks/useSearch';
import { FeatureArchivedCell } from '../../common/Table/cells/FeatureArchivedCell/FeatureArchivedCell';
export interface IFeaturesArchiveTableProps { export interface IFeaturesArchiveTableProps {
archivedFeatures: FeatureSchema[]; archivedFeatures: FeatureSchema[];
title: string;
refetch: any; refetch: any;
loading: boolean; loading: boolean;
storedParams: any; storedParams: any;
setStoredParams: any; setStoredParams: any;
searchParams: any; searchParams: any;
setSearchParams: any; setSearchParams: any;
title: string;
} }
export const ArchiveTable = ({ export const ArchiveTable = ({
@ -69,7 +61,11 @@ export const ArchiveTable = ({
const { reviveFeature } = useFeatureArchiveApi(); const { reviveFeature } = useFeatureArchiveApi();
const onRevive = async (feature: string) => { const [searchValue, setSearchValue] = useState(
searchParams.get('search') || ''
);
const onRevive = useCallback(async (feature: string) => {
try { try {
await reviveFeature(feature); await reviveFeature(feature);
await refetch(); await refetch();
@ -82,16 +78,95 @@ export const ArchiveTable = ({
} catch (e: unknown) { } catch (e: unknown) {
setToastApiError(formatUnknownError(e)); setToastApiError(formatUnknownError(e));
} }
}; }, []);
const columns = getColumns(onRevive); const columns = useMemo(
() => [
{
id: 'Seen',
Header: 'Seen',
maxWidth: 85,
canSort: true,
Cell: FeatureSeenCell,
accessor: 'lastSeenAt',
},
{
id: 'Type',
Header: 'Type',
maxWidth: 80,
canSort: true,
Cell: FeatureTypeCell,
},
{
Header: 'Feature toggle Name',
accessor: 'name',
minWidth: 100,
Cell: ({ value, row: { original } }: any) => (
<HighlightCell
value={value}
subtitle={original.description}
/>
),
sortType: 'alphanumeric',
},
{
Header: 'Created',
accessor: 'createdAt',
minWidth: 120,
Cell: DateCell,
sortType: 'date',
},
{
Header: 'Archived',
accessor: 'archivedAt',
minWidth: 120,
Cell: FeatureArchivedCell,
sortType: 'date',
},
{
Header: 'Project ID',
accessor: 'project',
sortType: 'alphanumeric',
maxWidth: 150,
Cell: ({ value }: any) => (
<LinkCell title={value} to={`/projects/${value}}`} />
),
},
{
Header: 'Status',
accessor: 'stale',
Cell: FeatureStaleCell,
sortType: 'boolean',
maxWidth: 120,
disableGlobalFilter: true,
},
{
Header: 'Actions',
id: 'Actions',
align: 'center',
maxWidth: 85,
canSort: false,
disableGlobalFilter: true,
Cell: ({ row: { original } }: any) => (
<ReviveArchivedFeatureCell
project={original.project}
onRevive={() => onRevive(original.name)}
/>
),
},
],
[]
);
const {
data: searchedData,
getSearchText,
getSearchContext,
} = useSearch(columns, searchValue, archivedFeatures);
const data = useMemo( const data = useMemo(
() => () => (loading ? featuresPlaceholder : searchedData),
archivedFeatures?.length === 0 && loading [searchedData, loading]
? featuresPlaceholder
: archivedFeatures,
[archivedFeatures, loading]
); );
const [initialState] = useState(() => ({ const [initialState] = useState(() => ({
@ -104,32 +179,27 @@ export const ArchiveTable = ({
}, },
], ],
hiddenColumns: ['description'], hiddenColumns: ['description'],
globalFilter: searchParams.get('search') || '',
})); }));
const { const {
getTableProps,
getTableBodyProps,
headerGroups, headerGroups,
rows, rows,
state: { sortBy },
getTableBodyProps,
getTableProps,
prepareRow, prepareRow,
state: { globalFilter, sortBy },
setGlobalFilter,
setHiddenColumns, setHiddenColumns,
} = useTable( } = useTable(
{ {
columns: columns as any, columns: columns as any[], // TODO: fix after `react-table` v8 update
data: data as any, data,
initialState, initialState,
sortTypes, sortTypes,
autoResetGlobalFilter: false,
autoResetSortBy: false,
disableSortRemove: true, disableSortRemove: true,
disableMultiSort: true, autoResetSortBy: false,
}, },
useGlobalFilter, useFlexLayout,
useSortBy, useSortBy
useFlexLayout
); );
useEffect(() => { useEffect(() => {
@ -144,35 +214,28 @@ export const ArchiveTable = ({
}, [setHiddenColumns, isSmallScreen, isMediumScreen]); }, [setHiddenColumns, isSmallScreen, isMediumScreen]);
useEffect(() => { useEffect(() => {
const tableState: PageQueryType = {}; if (loading) {
return;
}
const tableState: Record<string, string> = {};
tableState.sort = sortBy[0].id; tableState.sort = sortBy[0].id;
if (sortBy[0].desc) { if (sortBy[0].desc) {
tableState.order = 'desc'; tableState.order = 'desc';
} }
if (globalFilter) { if (searchValue) {
tableState.search = globalFilter; tableState.search = searchValue;
} }
setSearchParams(tableState, { setSearchParams(tableState, {
replace: true, replace: true,
}); });
setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false }); setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false });
}, [sortBy, globalFilter, setSearchParams, setStoredParams]); }, [loading, sortBy, searchValue, setSearchParams, setStoredParams]);
const [firstRenderedIndex, lastRenderedIndex] =
useVirtualizedRange(rowHeight);
const renderRows = () => { const renderRows = () => {
return ( return (
<> <>
{rows.map((row, index) => { {rows.map((row) => {
const isVirtual =
index < firstRenderedIndex || index > lastRenderedIndex;
if (isVirtual) {
return null;
}
prepareRow(row); prepareRow(row);
return ( return (
<TableRow hover {...row.getRowProps()}> <TableRow hover {...row.getRowProps()}>
@ -210,8 +273,10 @@ export const ArchiveTable = ({
actions={ actions={
<> <>
<TableSearch <TableSearch
initialValue={globalFilter} initialValue={searchValue}
onChange={setGlobalFilter} onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
/> />
</> </>
} }
@ -223,8 +288,10 @@ export const ArchiveTable = ({
show={<TablePlaceholder />} show={<TablePlaceholder />}
elseShow={() => ( elseShow={() => (
<> <>
<SearchHighlightProvider value={globalFilter}> <SearchHighlightProvider
<Table {...getTableProps()} rowHeight="standard"> value={getSearchText(searchValue)}
>
<Table {...getTableProps()} rowHeight={rowHeight}>
<SortableTableHeader <SortableTableHeader
headerGroups={headerGroups as any} headerGroups={headerGroups as any}
/> />
@ -235,12 +302,12 @@ export const ArchiveTable = ({
</SearchHighlightProvider> </SearchHighlightProvider>
<ConditionallyRender <ConditionallyRender
condition={ condition={
rows.length === 0 && globalFilter?.length > 0 rows.length === 0 && searchValue?.length > 0
} }
show={ show={
<TablePlaceholder> <TablePlaceholder>
No features found matching &ldquo; No feature toggles found matching &ldquo;
{globalFilter}&rdquo; {searchValue}&rdquo;
</TablePlaceholder> </TablePlaceholder>
} }
/> />
@ -250,80 +317,3 @@ export const ArchiveTable = ({
</PageContent> </PageContent>
); );
}; };
const getColumns = (onRevive: (feature: string) => Promise<void>) => {
return [
{
id: 'Seen',
Header: 'Seen',
maxWidth: 85,
canSort: true,
Cell: FeatureSeenCell,
disableGlobalFilter: true,
},
{
id: 'Type',
Header: 'Type',
maxWidth: 85,
canSort: true,
Cell: FeatureTypeCell,
disableGlobalFilter: true,
},
{
Header: 'Feature toggle Name',
accessor: 'name',
maxWidth: 150,
Cell: ({ value, row: { original } }: any) => (
<HighlightCell value={value} subtitle={original.description} />
),
sortType: 'alphanumeric',
},
{
Header: 'Created',
accessor: 'createdAt',
maxWidth: 150,
Cell: DateCell,
sortType: 'date',
disableGlobalFilter: true,
},
{
Header: 'Archived',
accessor: 'archivedAt',
maxWidth: 150,
Cell: TimeAgoCell,
sortType: 'date',
disableGlobalFilter: true,
},
{
Header: 'Project ID',
accessor: 'project',
sortType: 'alphanumeric',
maxWidth: 150,
Cell: ({ value }: any) => (
<LinkCell title={value} to={`/projects/${value}}`} />
),
},
{
Header: 'Status',
accessor: 'stale',
Cell: FeatureStaleCell,
sortType: 'boolean',
maxWidth: 120,
disableGlobalFilter: true,
},
{
Header: 'Actions',
id: 'Actions',
align: 'center',
maxWidth: 85,
canSort: false,
disableGlobalFilter: true,
Cell: ({ row: { original } }: any) => (
<ReviveArchivedFeatureCell
project={original.project}
onRevive={() => onRevive(original.name)}
/>
),
},
];
};

View File

@ -5,7 +5,7 @@ import PermissionIconButton from '../../../common/PermissionIconButton/Permissio
import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions'; import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions';
interface IReviveArchivedFeatureCell { interface IReviveArchivedFeatureCell {
onRevive: (event: SyntheticEvent<Element, Event>) => void; onRevive: () => void;
project: string; project: string;
} }
@ -13,10 +13,15 @@ export const ReviveArchivedFeatureCell: VFC<IReviveArchivedFeatureCell> = ({
onRevive, onRevive,
project, project,
}) => { }) => {
const handleClick = (e: SyntheticEvent<Element, Event>) => {
e.preventDefault();
onRevive();
};
return ( return (
<ActionCell> <ActionCell>
<PermissionIconButton <PermissionIconButton
onClick={onRevive} onClick={handleClick}
projectId={project} projectId={project}
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
tooltipProps={{ title: 'Revive feature' }} tooltipProps={{ title: 'Revive feature' }}

View File

@ -21,7 +21,7 @@ export const ProjectFeaturesArchiveTable = ({
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const [storedParams, setStoredParams] = useLocalStorage( const [storedParams, setStoredParams] = useLocalStorage(
'ProjectFeaturesArchiveTable:v1', `${projectId}:ProjectFeaturesArchiveTable`,
defaultSort defaultSort
); );

View File

@ -0,0 +1,33 @@
import React, {VFC} from 'react';
import TimeAgo from 'react-timeago';
import {Tooltip, Typography} from '@mui/material';
import {formatDateYMD} from '../../../../../utils/formatDate';
import {TextCell} from '../TextCell/TextCell';
import {useLocationSettings} from "../../../../../hooks/useLocationSettings";
interface IFeatureArchivedCellProps {
value?: string | Date | null;
}
export const FeatureArchivedCell: VFC<IFeatureArchivedCellProps> = ({
value: archivedAt,
}) => {
const { locationSettings } = useLocationSettings();
if (!archivedAt) return <TextCell />;
return (
<TextCell>
{archivedAt && (
<Tooltip
title={`Archived on: ${formatDateYMD(archivedAt, locationSettings.locale)}`}
arrow
>
<Typography noWrap variant="body2" data-loading>
<TimeAgo date={new Date(archivedAt)} title={''} />
</Typography>
</Tooltip>
)}
</TextCell>
);
};