mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
Feat: row actions (#5635)
- add table placeholder back - add row actions column - refactor actions into hook - batch actions
This commit is contained in:
parent
d886c910da
commit
29bd636273
@ -35,11 +35,7 @@ import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell';
|
||||
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
|
||||
import { IProject } from 'interfaces/project';
|
||||
import {
|
||||
PaginatedTable,
|
||||
TablePlaceholder,
|
||||
VirtualizedTable,
|
||||
} from 'component/common/Table';
|
||||
import { PaginatedTable, VirtualizedTable } from 'component/common/Table';
|
||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog';
|
||||
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
|
||||
@ -92,6 +88,9 @@ import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/Fe
|
||||
import { FeatureToggleCell } from './FeatureToggleCell/FeatureToggleCell';
|
||||
import { ProjectOverviewFilters } from './ProjectOverviewFilters';
|
||||
import { useDefaultColumnVisibility } from './hooks/useDefaultColumnVisibility';
|
||||
import { Placeholder } from './TablePlaceholder/TablePlaceholder';
|
||||
import { useRowActions } from './hooks/useRowActions';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
interface IExperimentalProjectFeatureTogglesProps {
|
||||
environments: IProject['environments'];
|
||||
@ -111,6 +110,8 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
}: IExperimentalProjectFeatureTogglesProps) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
|
||||
const featuresExportImport = useUiFlag('featuresExportImport');
|
||||
|
||||
const stateConfig = {
|
||||
offset: withDefault(NumberParam, 0),
|
||||
limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
|
||||
@ -160,7 +161,13 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
const { onToggle: onFeatureToggle, modals: featureToggleModals } =
|
||||
useFeatureToggleSwitch(projectId);
|
||||
const bodyLoadingRef = useLoading(loading);
|
||||
const {
|
||||
rowActionsDialogs,
|
||||
setFeatureArchiveState,
|
||||
setFeatureStaleDialogState,
|
||||
} = useRowActions(refetch, projectId);
|
||||
const [showExportDialog, setShowExportDialog] = useState(false);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
@ -181,6 +188,9 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
onChange={row?.getToggleSelectedHandler()}
|
||||
/>
|
||||
),
|
||||
meta: {
|
||||
width: '1%',
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('favorite', {
|
||||
id: 'favorite',
|
||||
@ -203,6 +213,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
enableSorting: false,
|
||||
meta: {
|
||||
align: 'center',
|
||||
width: '1%',
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('lastSeenAt', {
|
||||
@ -217,6 +228,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
size: 50,
|
||||
meta: {
|
||||
align: 'center',
|
||||
width: '1%',
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('type', {
|
||||
@ -225,12 +237,14 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
cell: FeatureTypeCell,
|
||||
meta: {
|
||||
align: 'center',
|
||||
width: '1%',
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('name', {
|
||||
id: 'name',
|
||||
header: 'Name',
|
||||
cell: FeatureNameCell,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
width: '50%',
|
||||
},
|
||||
@ -266,6 +280,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
header: name,
|
||||
meta: {
|
||||
align: 'center',
|
||||
width: '1%',
|
||||
},
|
||||
cell: ({ getValue }) => {
|
||||
const {
|
||||
@ -296,6 +311,24 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
);
|
||||
},
|
||||
),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
header: '',
|
||||
cell: ({ row }) => (
|
||||
<ActionsCell
|
||||
row={row}
|
||||
projectId={projectId}
|
||||
onOpenArchiveDialog={setFeatureArchiveState}
|
||||
onOpenStaleDialog={setFeatureStaleDialogState}
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
align: 'right',
|
||||
width: '1%',
|
||||
},
|
||||
}),
|
||||
],
|
||||
[projectId, environments, tableState.favoritesFirst, refetch],
|
||||
);
|
||||
@ -323,12 +356,15 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
[tableState.limit],
|
||||
);
|
||||
|
||||
const isPlaceholder = Boolean(initialLoad || (loading && total));
|
||||
const bodyLoadingRef = useLoading(isPlaceholder);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (initialLoad || (loading && total)) {
|
||||
if (isPlaceholder) {
|
||||
return placeholderData;
|
||||
}
|
||||
return features;
|
||||
}, [loading, features]);
|
||||
}, [isPlaceholder, features]);
|
||||
const allColumnIds = useMemo(
|
||||
() => columns.map((column) => column.id).filter(Boolean) as string[],
|
||||
[columns],
|
||||
@ -347,7 +383,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
}),
|
||||
);
|
||||
|
||||
const { columnVisibility } = table.getState();
|
||||
const { columnVisibility, rowSelection } = table.getState();
|
||||
const onToggleColumnVisibility = useCallback(
|
||||
(columnId) => {
|
||||
const isVisible = columnVisibility[columnId];
|
||||
@ -429,7 +465,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
>
|
||||
<div
|
||||
ref={bodyLoadingRef}
|
||||
aria-busy={loading}
|
||||
aria-busy={isPlaceholder}
|
||||
aria-live='polite'
|
||||
>
|
||||
<ProjectOverviewFilters
|
||||
@ -442,59 +478,13 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
totalItems={total}
|
||||
/>
|
||||
</SearchHighlightProvider>
|
||||
{/*
|
||||
<Placeholder total={total} query={tableState.query || ''} />
|
||||
{rowActionsDialogs}
|
||||
|
||||
<ConditionallyRender
|
||||
condition={rows.length === 0}
|
||||
show={
|
||||
<ConditionallyRender
|
||||
condition={(tableState.query || '')?.length > 0}
|
||||
show={
|
||||
<Box sx={{ padding: theme.spacing(3) }}>
|
||||
<TablePlaceholder>
|
||||
No feature toggles found matching
|
||||
“
|
||||
{tableState.query}
|
||||
”
|
||||
</TablePlaceholder>
|
||||
</Box>
|
||||
}
|
||||
elseShow={
|
||||
<Box sx={{ padding: theme.spacing(3) }}>
|
||||
<TablePlaceholder>
|
||||
No feature toggles available. Get
|
||||
started by adding a new feature
|
||||
toggle.
|
||||
</TablePlaceholder>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<FeatureStaleDialog
|
||||
isStale={featureStaleDialogState.stale === true}
|
||||
isOpen={Boolean(featureStaleDialogState.featureId)}
|
||||
onClose={() => {
|
||||
setFeatureStaleDialogState({});
|
||||
onChange();
|
||||
}}
|
||||
featureId={featureStaleDialogState.featureId || ''}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<FeatureArchiveDialog
|
||||
isOpen={Boolean(featureArchiveState)}
|
||||
onConfirm={onChange}
|
||||
onClose={() => {
|
||||
setFeatureArchiveState(undefined);
|
||||
}}
|
||||
featureIds={[featureArchiveState || '']}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
Boolean(uiConfig?.flags?.featuresExportImport) &&
|
||||
!loading
|
||||
}
|
||||
condition={featuresExportImport && !loading}
|
||||
show={
|
||||
// FIXME: export only selected rows?
|
||||
<ExportDialog
|
||||
showExportDialog={showExportDialog}
|
||||
data={data}
|
||||
@ -505,19 +495,17 @@ export const ExperimentalProjectFeatureToggles = ({
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{featureToggleModals} */}
|
||||
{featureToggleModals}
|
||||
</div>
|
||||
</PageContent>
|
||||
{/* <BatchSelectionActionsBar
|
||||
count={Object.keys(selectedRowIds).length}
|
||||
>
|
||||
<BatchSelectionActionsBar count={Object.keys(rowSelection).length}>
|
||||
<ProjectFeaturesBatchActions
|
||||
selectedIds={Object.keys(selectedRowIds)}
|
||||
selectedIds={Object.keys(rowSelection)}
|
||||
data={features}
|
||||
projectId={projectId}
|
||||
onResetSelection={() => toggleAllRowsSelected(false)}
|
||||
onResetSelection={table.resetRowSelection}
|
||||
/>
|
||||
</BatchSelectionActionsBar> */}
|
||||
</BatchSelectionActionsBar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,43 @@
|
||||
import { FC } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { TablePlaceholder } from 'component/common/Table';
|
||||
|
||||
interface ITablePlaceholderProps {
|
||||
total?: number;
|
||||
query?: string;
|
||||
}
|
||||
|
||||
export const Placeholder: FC<ITablePlaceholderProps> = ({ total, query }) => {
|
||||
if (total !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((query || '')?.length > 0) {
|
||||
return (
|
||||
<Box
|
||||
sx={(theme) => ({
|
||||
padding: theme.spacing(3),
|
||||
})}
|
||||
>
|
||||
<TablePlaceholder>
|
||||
No feature toggles found matching “
|
||||
{query}
|
||||
”
|
||||
</TablePlaceholder>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={(theme) => ({
|
||||
padding: theme.spacing(3),
|
||||
})}
|
||||
>
|
||||
<TablePlaceholder>
|
||||
No feature toggles available. Get started by adding a new
|
||||
feature toggle.
|
||||
</TablePlaceholder>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -0,0 +1,45 @@
|
||||
import { useState } from 'react';
|
||||
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
|
||||
import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog';
|
||||
|
||||
export const useRowActions = (onChange: () => void, projectId: string) => {
|
||||
const [featureArchiveState, setFeatureArchiveState] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
|
||||
const [featureStaleDialogState, setFeatureStaleDialogState] = useState<{
|
||||
featureId?: string;
|
||||
stale?: boolean;
|
||||
}>({});
|
||||
|
||||
const rowActionsDialogs = (
|
||||
<>
|
||||
<FeatureStaleDialog
|
||||
isStale={Boolean(featureStaleDialogState.stale)}
|
||||
isOpen={Boolean(featureStaleDialogState.featureId)}
|
||||
onClose={() => {
|
||||
setFeatureStaleDialogState({});
|
||||
onChange();
|
||||
}}
|
||||
featureId={featureStaleDialogState.featureId || ''}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
<FeatureArchiveDialog
|
||||
isOpen={Boolean(featureArchiveState)}
|
||||
onConfirm={onChange}
|
||||
onClose={() => {
|
||||
setFeatureArchiveState(undefined);
|
||||
}}
|
||||
featureIds={[featureArchiveState || '']}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return {
|
||||
rowActionsDialogs,
|
||||
setFeatureArchiveState,
|
||||
setFeatureStaleDialogState,
|
||||
};
|
||||
};
|
@ -304,4 +304,42 @@ describe('withTableState', () => {
|
||||
|
||||
expect(getByTestId('sort')).toHaveValue('createdAt');
|
||||
});
|
||||
|
||||
it('always shows columns that have `enableHiding: false`', () => {
|
||||
const mockTableState = {
|
||||
limit: 10,
|
||||
offset: 10,
|
||||
sortBy: 'name',
|
||||
sortOrder: 'asc',
|
||||
columns: ['createdAt'],
|
||||
};
|
||||
const mockSetTableState = vi.fn();
|
||||
const mockOptions = {
|
||||
data: [],
|
||||
columns: [
|
||||
{
|
||||
id: 'name',
|
||||
show: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
id: 'createdAt',
|
||||
show: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = withTableState(
|
||||
mockTableState,
|
||||
mockSetTableState,
|
||||
mockOptions,
|
||||
);
|
||||
|
||||
expect(result.state).toMatchObject({
|
||||
columnVisibility: {
|
||||
name: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -169,10 +169,16 @@ export const withTableState = <T extends Object>(
|
||||
false,
|
||||
]),
|
||||
);
|
||||
const showAlwaysVisibleColumns = Object.fromEntries(
|
||||
options.columns
|
||||
.filter(({ enableHiding }) => enableHiding === false)
|
||||
.map((column) => [column.id, true]),
|
||||
);
|
||||
const columnVisibility = tableState.columns
|
||||
? {
|
||||
...hideAllColumns,
|
||||
...createColumnVisibilityState(tableState).columnVisibility,
|
||||
...showAlwaysVisibleColumns,
|
||||
}
|
||||
: options.state?.columnVisibility;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user