Skip to content

Commit

Permalink
Feat: row actions (#5635)
Browse files Browse the repository at this point in the history
- add table placeholder back
- add row actions column
- refactor actions into hook
- batch actions
  • Loading branch information
Tymek committed Dec 14, 2023
1 parent d886c91 commit 29bd636
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 68 deletions.
Expand Up @@ -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';
Expand Down Expand Up @@ -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'];
Expand All @@ -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),
Expand Down Expand Up @@ -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({
Expand All @@ -181,6 +188,9 @@ export const ExperimentalProjectFeatureToggles = ({
onChange={row?.getToggleSelectedHandler()}
/>
),
meta: {
width: '1%',
},
}),
columnHelper.accessor('favorite', {
id: 'favorite',
Expand All @@ -203,6 +213,7 @@ export const ExperimentalProjectFeatureToggles = ({
enableSorting: false,
meta: {
align: 'center',
width: '1%',
},
}),
columnHelper.accessor('lastSeenAt', {
Expand All @@ -217,6 +228,7 @@ export const ExperimentalProjectFeatureToggles = ({
size: 50,
meta: {
align: 'center',
width: '1%',
},
}),
columnHelper.accessor('type', {
Expand All @@ -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%',
},
Expand Down Expand Up @@ -266,6 +280,7 @@ export const ExperimentalProjectFeatureToggles = ({
header: name,
meta: {
align: 'center',
width: '1%',
},
cell: ({ getValue }) => {
const {
Expand Down Expand Up @@ -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],
);
Expand Down Expand Up @@ -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],
Expand All @@ -347,7 +383,7 @@ export const ExperimentalProjectFeatureToggles = ({
}),
);

const { columnVisibility } = table.getState();
const { columnVisibility, rowSelection } = table.getState();
const onToggleColumnVisibility = useCallback(
(columnId) => {
const isVisible = columnVisibility[columnId];
Expand Down Expand Up @@ -429,7 +465,7 @@ export const ExperimentalProjectFeatureToggles = ({
>
<div
ref={bodyLoadingRef}
aria-busy={loading}
aria-busy={isPlaceholder}
aria-live='polite'
>
<ProjectOverviewFilters
Expand All @@ -442,59 +478,13 @@ export const ExperimentalProjectFeatureToggles = ({
totalItems={total}
/>
</SearchHighlightProvider>
{/*
<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
&ldquo;
{tableState.query}
&rdquo;
</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}
/>
<Placeholder total={total} query={tableState.query || ''} />
{rowActionsDialogs}

<ConditionallyRender
condition={
Boolean(uiConfig?.flags?.featuresExportImport) &&
!loading
}
condition={featuresExportImport && !loading}
show={
// FIXME: export only selected rows?
<ExportDialog
showExportDialog={showExportDialog}
data={data}
Expand All @@ -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 &ldquo;
{query}
&rdquo;
</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,
};
};
38 changes: 38 additions & 0 deletions frontend/src/utils/withTableState.test.tsx
Expand Up @@ -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,
},
});
});
});
6 changes: 6 additions & 0 deletions frontend/src/utils/withTableState.ts
Expand Up @@ -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;

Expand Down

0 comments on commit 29bd636

Please sign in to comment.