From 29bd636273bc34f3cd0245a0227f422603db5498 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Thu, 14 Dec 2023 09:04:56 +0100 Subject: [PATCH] Feat: row actions (#5635) - add table placeholder back - add row actions column - refactor actions into hook - batch actions --- .../ExperimentalProjectTable.tsx | 124 ++++++++---------- .../TablePlaceholder/TablePlaceholder.tsx | 43 ++++++ .../hooks/useRowActions.tsx | 45 +++++++ frontend/src/utils/withTableState.test.tsx | 38 ++++++ frontend/src/utils/withTableState.ts | 6 + 5 files changed, 188 insertions(+), 68 deletions(-) create mode 100644 frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/TablePlaceholder/TablePlaceholder.tsx create mode 100644 frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/hooks/useRowActions.tsx diff --git a/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/ExperimentalProjectTable.tsx b/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/ExperimentalProjectTable.tsx index 0e6a5f3d7d..cb9f3c9d7b 100644 --- a/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/ExperimentalProjectTable.tsx +++ b/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/ExperimentalProjectTable.tsx @@ -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 }) => ( + + ), + 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 = ({ >
- {/* + + {rowActionsDialogs} + 0} - show={ - - - No feature toggles found matching - “ - {tableState.query} - ” - - - } - elseShow={ - - - No feature toggles available. Get - started by adding a new feature - toggle. - - - } - /> - } - /> - { - setFeatureStaleDialogState({}); - onChange(); - }} - featureId={featureStaleDialogState.featureId || ''} - projectId={projectId} - /> - { - setFeatureArchiveState(undefined); - }} - featureIds={[featureArchiveState || '']} - projectId={projectId} - /> - } /> - {featureToggleModals} */} + {featureToggleModals}
- {/* + toggleAllRowsSelected(false)} + onResetSelection={table.resetRowSelection} /> - */} + ); }; diff --git a/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/TablePlaceholder/TablePlaceholder.tsx b/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/TablePlaceholder/TablePlaceholder.tsx new file mode 100644 index 0000000000..6351c88b18 --- /dev/null +++ b/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/TablePlaceholder/TablePlaceholder.tsx @@ -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 = ({ total, query }) => { + if (total !== 0) { + return null; + } + + if ((query || '')?.length > 0) { + return ( + ({ + padding: theme.spacing(3), + })} + > + + No feature toggles found matching “ + {query} + ” + + + ); + } + + return ( + ({ + padding: theme.spacing(3), + })} + > + + No feature toggles available. Get started by adding a new + feature toggle. + + + ); +}; diff --git a/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/hooks/useRowActions.tsx b/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/hooks/useRowActions.tsx new file mode 100644 index 0000000000..de10233345 --- /dev/null +++ b/frontend/src/component/project/Project/ExperimentalProjectFeatures/ExperimentalProjectTable/hooks/useRowActions.tsx @@ -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 = ( + <> + { + setFeatureStaleDialogState({}); + onChange(); + }} + featureId={featureStaleDialogState.featureId || ''} + projectId={projectId} + /> + + { + setFeatureArchiveState(undefined); + }} + featureIds={[featureArchiveState || '']} + projectId={projectId} + /> + + ); + + return { + rowActionsDialogs, + setFeatureArchiveState, + setFeatureStaleDialogState, + }; +}; diff --git a/frontend/src/utils/withTableState.test.tsx b/frontend/src/utils/withTableState.test.tsx index caeea0cf18..ff3d0e778a 100644 --- a/frontend/src/utils/withTableState.test.tsx +++ b/frontend/src/utils/withTableState.test.tsx @@ -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, + }, + }); + }); }); diff --git a/frontend/src/utils/withTableState.ts b/frontend/src/utils/withTableState.ts index 9731a62d4c..e6df475073 100644 --- a/frontend/src/utils/withTableState.ts +++ b/frontend/src/utils/withTableState.ts @@ -169,10 +169,16 @@ export const withTableState = ( 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;