1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-01 01:18:10 +02:00
unleash.unleash/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx
2023-04-28 11:18:03 +03:00

363 lines
13 KiB
TypeScript

import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
import {
SortingRule,
useFlexLayout,
useRowSelect,
useSortBy,
useTable,
} from 'react-table';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { Checkbox, useMediaQuery } from '@mui/material';
import { sortTypes } from 'utils/sortTypes';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Search } from 'component/common/Search/Search';
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell';
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
import { FeatureStaleCell } from 'component/feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell';
import { ArchivedFeatureActionCell } from 'component/archive/ArchiveTable/ArchivedFeatureActionCell/ArchivedFeatureActionCell';
import { featuresPlaceholder } from 'component/feature/FeatureToggleList/FeatureToggleListTable';
import theme from 'themes/theme';
import { FeatureSchema } from 'openapi';
import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useSearch } from 'hooks/useSearch';
import { FeatureArchivedCell } from './FeatureArchivedCell/FeatureArchivedCell';
import { useSearchParams } from 'react-router-dom';
import { ArchivedFeatureDeleteConfirm } from './ArchivedFeatureActionCell/ArchivedFeatureDeleteConfirm/ArchivedFeatureDeleteConfirm';
import { IFeatureToggle } from 'interfaces/featureToggle';
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
import { RowSelectCell } from '../../project/Project/ProjectFeatureToggles/RowSelectCell/RowSelectCell';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { BatchSelectionActionsBar } from '../../common/BatchSelectionActionsBar/BatchSelectionActionsBar';
import { ArchiveBatchActions } from './ArchiveBatchActions';
export interface IFeaturesArchiveTableProps {
archivedFeatures: FeatureSchema[];
title: string;
refetch: () => void;
loading: boolean;
storedParams: SortingRule<string>;
setStoredParams: (
newValue:
| SortingRule<string>
| ((prev: SortingRule<string>) => SortingRule<string>)
) => SortingRule<string>;
projectId?: string;
}
export const ArchiveTable = ({
archivedFeatures = [],
loading,
refetch,
storedParams,
setStoredParams,
title,
projectId,
}: IFeaturesArchiveTableProps) => {
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const [deletedFeature, setDeletedFeature] = useState<IFeatureToggle>();
const [searchParams, setSearchParams] = useSearchParams();
const { reviveFeature } = useFeatureArchiveApi();
const [searchValue, setSearchValue] = useState(
searchParams.get('search') || ''
);
const onRevive = useCallback(
async (feature: string) => {
try {
await reviveFeature(feature);
await refetch();
setToastData({
type: 'success',
title: "And we're back!",
text: 'The feature toggle has been revived.',
});
} catch (e: unknown) {
setToastApiError(formatUnknownError(e));
}
},
[refetch, reviveFeature, setToastApiError, setToastData]
);
const columns = useMemo(
() => [
{
id: 'Select',
Header: ({ getToggleAllRowsSelectedProps }: any) => (
<Checkbox {...getToggleAllRowsSelectedProps()} />
),
Cell: ({ row }: any) => (
<RowSelectCell {...row?.getToggleRowSelectedProps?.()} />
),
maxWidth: 50,
disableSortBy: true,
hideInMenu: true,
},
{
Header: 'Seen',
width: 85,
canSort: true,
Cell: FeatureSeenCell,
accessor: 'lastSeenAt',
align: 'center',
},
{
Header: 'Type',
accessor: 'type',
width: 85,
canSort: true,
Cell: FeatureTypeCell,
align: 'center',
},
{
Header: 'Name',
accessor: 'name',
searchable: true,
minWidth: 100,
Cell: ({ value, row: { original } }: any) => (
<HighlightCell
value={value}
subtitle={original.description}
/>
),
sortType: 'alphanumeric',
},
{
Header: 'Created',
accessor: 'createdAt',
width: 150,
Cell: DateCell,
sortType: 'date',
},
{
Header: 'Archived',
accessor: 'archivedAt',
width: 150,
Cell: FeatureArchivedCell,
sortType: 'date',
},
...(!projectId
? [
{
Header: 'Project ID',
accessor: 'project',
sortType: 'alphanumeric',
filterName: 'project',
searchable: true,
maxWidth: 170,
Cell: ({ value }: any) => (
<LinkCell
title={value}
to={`/projects/${value}`}
/>
),
},
]
: []),
{
Header: 'State',
accessor: 'stale',
Cell: FeatureStaleCell,
sortType: 'boolean',
maxWidth: 120,
filterName: 'state',
filterParsing: (value: any) => (value ? 'stale' : 'active'),
},
{
Header: 'Actions',
id: 'Actions',
align: 'center',
maxWidth: 120,
canSort: false,
Cell: ({ row: { original: feature } }: any) => (
<ArchivedFeatureActionCell
project={feature.project}
onRevive={() => onRevive(feature.name)}
onDelete={() => {
setDeletedFeature(feature);
setDeleteModalOpen(true);
}}
/>
),
},
// Always hidden -- for search
{
accessor: 'description',
header: 'Description',
searchable: true,
},
],
//eslint-disable-next-line
[projectId]
);
const {
data: searchedData,
getSearchText,
getSearchContext,
} = useSearch(columns, searchValue, archivedFeatures);
const data = useMemo(
() => (loading ? featuresPlaceholder : searchedData),
[searchedData, loading]
);
const [initialState] = useState(() => ({
sortBy: [
{
id: searchParams.get('sort') || storedParams.id,
desc: searchParams.has('order')
? searchParams.get('order') === 'desc'
: storedParams.desc,
},
],
hiddenColumns: ['description'],
selectedRowIds: {},
}));
const getRowId = useCallback((row: any) => row.name, []);
const {
headerGroups,
rows,
state: { sortBy, selectedRowIds },
prepareRow,
setHiddenColumns,
} = useTable(
{
columns: columns as any[], // TODO: fix after `react-table` v8 update
data,
initialState,
sortTypes,
autoResetHiddenColumns: false,
disableSortRemove: true,
autoResetSortBy: false,
getRowId,
},
useFlexLayout,
useSortBy,
useRowSelect
);
useConditionallyHiddenColumns(
[
{
condition: isSmallScreen,
columns: ['type', 'createdAt'],
},
{
condition: isMediumScreen,
columns: ['lastSeenAt', 'stale'],
},
],
setHiddenColumns,
columns
);
useEffect(() => {
if (loading) {
return;
}
const tableState: Record<string, string> = {};
tableState.sort = sortBy[0].id;
if (sortBy[0].desc) {
tableState.order = 'desc';
}
if (searchValue) {
tableState.search = searchValue;
}
setSearchParams(tableState, {
replace: true,
});
setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false });
}, [loading, sortBy, searchValue]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<>
<PageContent
isLoading={loading}
header={
<PageHeader
titleElement={`${title} (${
rows.length < data.length
? `${rows.length} of ${data.length}`
: data.length
})`}
actions={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
/>
}
/>
}
>
<SearchHighlightProvider value={getSearchText(searchValue)}>
<VirtualizedTable
rows={rows}
headerGroups={headerGroups}
prepareRow={prepareRow}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={rows.length === 0}
show={() => (
<ConditionallyRender
condition={searchValue?.length > 0}
show={
<TablePlaceholder>
No feature toggles found matching &ldquo;
{searchValue}&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
None of the feature toggles were archived
yet.
</TablePlaceholder>
}
/>
)}
/>
<ArchivedFeatureDeleteConfirm
deletedFeatures={[deletedFeature?.name!]}
projectId={projectId!}
open={deleteModalOpen}
setOpen={setDeleteModalOpen}
refetch={refetch}
/>
</PageContent>
<ConditionallyRender
condition={Boolean(projectId)}
show={
<BatchSelectionActionsBar
count={Object.keys(selectedRowIds).length}
>
<ArchiveBatchActions
selectedIds={Object.keys(selectedRowIds)}
projectId={projectId!}
/>
</BatchSelectionActionsBar>
}
/>
</>
);
};