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:
parent
c26bfcf9da
commit
854ffaf0b2
@ -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 “
|
No feature toggles found matching “
|
||||||
{globalFilter}”
|
{searchValue}”
|
||||||
</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)}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
@ -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' }}
|
||||||
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user