import { useCallback, useMemo, useState } from 'react'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { PageContent } from 'component/common/PageContent/PageContent'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { PaginatedTable } from 'component/common/Table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader'; import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell'; import { ActionsCell } from '../ProjectFeatureToggles/ActionsCell/ActionsCell'; import { ExperimentalColumnsMenu as ColumnsMenu } from './ExperimentalColumnsMenu/ExperimentalColumnsMenu'; import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi'; import { MemoizedRowSelectCell } from '../ProjectFeatureToggles/RowSelectCell/RowSelectCell'; import { BatchSelectionActionsBar } from 'component/common/BatchSelectionActionsBar/BatchSelectionActionsBar'; import { ProjectFeaturesBatchActions } from '../ProjectFeatureToggles/ProjectFeaturesBatchActions/ProjectFeaturesBatchActions'; import { FeatureLifecycleCell, MemoizedFeatureEnvironmentSeenCell, } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useFeatureToggleSwitch } from '../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch'; import useLoading from 'hooks/useLoading'; import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader'; import { createColumnHelper, useReactTable } from '@tanstack/react-table'; import { withTableState } from 'utils/withTableState'; import type { FeatureSearchResponseSchema } from 'openapi'; import { FeatureToggleCell, PlaceholderFeatureToggleCell, } from './FeatureToggleCell/FeatureToggleCell'; import { ProjectOverviewFilters } from './ProjectOverviewFilters'; import { useDefaultColumnVisibility } from './hooks/useDefaultColumnVisibility'; import { TableEmptyState } from './TableEmptyState/TableEmptyState'; import { useRowActions } from './hooks/useRowActions'; import { useSelectedData } from './hooks/useSelectedData'; import { FeatureOverviewCell } from 'component/common/Table/cells/FeatureOverviewCell/FeatureOverviewCell'; import { useProjectFeatureSearch, useProjectFeatureSearchActions, } from './useProjectFeatureSearch'; import { AvatarCell } from './AvatarCell'; import { useUiFlag } from 'hooks/useUiFlag'; import { styled } from '@mui/material'; import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; import { ConnectSdkDialog } from '../../../onboarding/dialog/ConnectSdkDialog'; import { ProjectOnboarding } from '../../../onboarding/flow/ProjectOnboarding'; import { useLocalStorageState } from 'hooks/useLocalStorageState'; import { ProjectOnboarded } from 'component/onboarding/flow/ProjectOnboarded'; interface IPaginatedProjectFeatureTogglesProps { environments: string[]; } const formatEnvironmentColumnId = (environment: string) => `environment:${environment}`; const columnHelper = createColumnHelper(); const getRowId = (row: { name: string }) => row.name; const Container = styled('div')(({ theme }) => ({ display: 'flex', flexDirection: 'column', gap: theme.spacing(2), })); export const ProjectFeatureToggles = ({ environments, }: IPaginatedProjectFeatureTogglesProps) => { const onboardingUIEnabled = useUiFlag('onboardingUI'); const projectId = useRequiredPathParam('projectId'); const { project } = useProjectOverview(projectId); const [connectSdkOpen, setConnectSdkOpen] = useState(false); const { features, total, refetch, loading, initialLoad, tableState, setTableState, } = useProjectFeatureSearch(projectId); const { onFlagTypeClick, onTagClick, onAvatarClick } = useProjectFeatureSearchActions(tableState, setTableState); const filterState = { tag: tableState.tag, createdAt: tableState.createdAt, type: tableState.type, state: tableState.state, createdBy: tableState.createdBy, }; const { favorite, unfavorite } = useFavoriteFeaturesApi(); const onFavorite = useCallback( async (feature: FeatureSearchResponseSchema) => { if (feature?.favorite) { await unfavorite(projectId, feature.name); } else { await favorite(projectId, feature.name); } refetch(); }, [projectId, refetch], ); const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const { onToggle: onFeatureToggle, modals: featureToggleModals } = useFeatureToggleSwitch(projectId); const { rowActionsDialogs, setFeatureArchiveState, setFeatureStaleDialogState, setShowMarkCompletedDialogue, } = useRowActions(refetch, projectId); const isPlaceholder = Boolean(initialLoad || (loading && total)); const [onboardingFlow, setOnboardingFlow] = useLocalStorageState< 'visible' | 'closed' >(`onboarding-flow:v1-${projectId}`, 'visible'); const [setupCompletedState, setSetupCompletedState] = useLocalStorageState< 'hide-setup' | 'show-setup' >(`onboarding-state:v1-${projectId}`, 'hide-setup'); const notOnboarding = !onboardingUIEnabled || (onboardingUIEnabled && project.onboardingStatus.status === 'onboarded') || onboardingFlow === 'closed'; const isOnboarding = onboardingUIEnabled && project.onboardingStatus.status !== 'onboarded' && onboardingFlow === 'visible'; const showFeaturesTable = (total !== undefined && total > 0) || notOnboarding; const columns = useMemo( () => [ columnHelper.display({ id: 'select', header: ({ table }) => ( ), cell: ({ row }) => ( ), meta: { width: '1%', }, enableHiding: false, }), columnHelper.accessor('favorite', { id: 'favorite', header: () => ( setTableState({ favoritesFirst: !tableState.favoritesFirst, }) } /> ), cell: ({ row: { original: feature } }) => ( onFavorite(feature)} /> ), enableSorting: false, enableHiding: false, meta: { align: 'center', width: '1%', }, }), columnHelper.accessor('name', { id: 'name', header: 'Name', cell: FeatureOverviewCell(onTagClick, onFlagTypeClick), enableHiding: false, meta: { width: '50%', }, }), columnHelper.accessor('createdAt', { id: 'createdAt', header: 'Created', cell: DateCell, meta: { width: '1%', }, }), columnHelper.accessor('createdBy', { id: 'createdBy', header: 'By', cell: AvatarCell(onAvatarClick), enableSorting: false, meta: { width: '1%', align: 'center', }, }), columnHelper.accessor('lastSeenAt', { id: 'lastSeenAt', header: 'Last seen', cell: ({ row: { original } }) => ( ), size: 50, meta: { align: 'center', width: '1%', }, }), columnHelper.accessor('lifecycle', { id: 'lifecycle', header: 'Lifecycle', cell: ({ row: { original } }) => ( { setShowMarkCompletedDialogue({ featureId: original.name, open: true, }); }} onUncomplete={refetch} onArchive={() => setFeatureArchiveState(original.name)} data-loading /> ), enableSorting: false, size: 50, meta: { align: 'center', width: '1%', }, }), ...environments.map((name: string) => { const isChangeRequestEnabled = isChangeRequestConfigured(name); return columnHelper.accessor( (row) => ({ featureId: row.name, environment: row.environments?.find( (featureEnvironment) => featureEnvironment.name === name, ), someEnabledEnvironmentHasVariants: row.environments?.some( (featureEnvironment) => featureEnvironment.variantCount && featureEnvironment.variantCount > 0 && featureEnvironment.enabled, ) || false, }), { id: formatEnvironmentColumnId(name), header: name, meta: { align: 'center', width: 90, }, cell: ({ getValue }) => { const { featureId, environment, someEnabledEnvironmentHasVariants, } = getValue(); return isPlaceholder ? ( ) : ( ); }, }, ); }), columnHelper.display({ id: 'actions', header: '', cell: ({ row }) => ( ), enableSorting: false, enableHiding: false, meta: { align: 'right', width: '1%', }, }), ], [ projectId, environments, tableState.favoritesFirst, refetch, isPlaceholder, ], ); const placeholderData = useMemo( () => Array(tableState.limit) .fill(null) .map((_, index) => ({ id: index, type: '-', name: `Feature name ${index}`, description: '', createdAt: new Date().toISOString(), createdBy: { id: 0, name: '', imageUrl: '', }, dependencyType: null, favorite: false, impressionData: false, project: 'project', segments: [], stale: false, environments: [ { name: 'development', enabled: false, type: 'development', }, { name: 'production', enabled: false, type: 'production', }, ], })), [tableState.limit], ); const bodyLoadingRef = useLoading(isPlaceholder); const data = useMemo(() => { if (isPlaceholder) { return placeholderData; } return features; }, [isPlaceholder, JSON.stringify(features)]); const allColumnIds = useMemo( () => columns.map((column) => column.id).filter(Boolean) as string[], [columns], ); const defaultColumnVisibility = useDefaultColumnVisibility(allColumnIds); const table = useReactTable( withTableState(tableState, setTableState, { columns, data, enableRowSelection: true, state: { columnVisibility: defaultColumnVisibility, }, getRowId, }), ); const { columnVisibility, rowSelection } = table.getState(); const onToggleColumnVisibility = useCallback( (columnId: string) => { const isVisible = columnVisibility[columnId]; const newColumnVisibility: Record = { ...columnVisibility, [columnId]: !isVisible, }; setTableState({ columns: Object.keys(newColumnVisibility).filter( (columnId) => newColumnVisibility[columnId] && !columnId.includes(','), ), }); }, [columnVisibility, setTableState], ); const selectedData = useSelectedData(features, rowSelection); return ( } /> { setSetupCompletedState('hide-setup'); }} /> } /> { setTableState({ query }); }} dataToExport={data} environmentsToExport={environments} actions={ ({ header: environment, id: formatEnvironmentColumnId( environment, ), isVisible: columnVisibility[ formatEnvironmentColumnId( environment, ) ], }), ), ]} onToggle={onToggleColumnVisibility} /> } /> } bodyClass='noop' style={{ cursor: 'inherit' }} >
} /> {rowActionsDialogs} {featureToggleModals}
} /> { setConnectSdkOpen(false); }} onFinish={() => { setConnectSdkOpen(false); setSetupCompletedState('show-setup'); }} project={projectId} environments={environments} feature={ 'feature' in project.onboardingStatus ? project.onboardingStatus.feature : undefined } />
); };