import { useCallback, useEffect, useMemo, useState, VFC } from 'react'; import { IconButton, Link, Tooltip, useMediaQuery, useTheme, } from '@mui/material'; import { Link as RouterLink, useSearchParams } from 'react-router-dom'; import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; import { TablePlaceholder, VirtualizedTable } from 'component/common/Table'; import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell'; import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/FeatureNameCell'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { createLocalStorage } from 'utils/createLocalStorage'; import { FeatureSchema } from 'openapi'; import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton'; import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell'; import { useSearch } from 'hooks/useSearch'; import { Search } from 'component/common/Search/Search'; import { FeatureTagCell } from 'component/common/Table/cells/FeatureTagCell/FeatureTagCell'; import { usePinnedFavorites } from 'hooks/usePinnedFavorites'; import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi'; import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell'; import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader'; import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage'; import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns'; import FileDownload from '@mui/icons-material/FileDownload'; import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; import { ExportDialog } from './ExportDialog'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { focusable } from 'themes/themeStyles'; import { FeatureEnvironmentSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell'; import useToast from 'hooks/useToast'; export const featuresPlaceholder: FeatureSchema[] = Array(15).fill({ name: 'Name of the feature', description: 'Short description of the feature', type: '-', createdAt: new Date(2022, 1, 1), project: 'projectID', }); export type PageQueryType = Partial< Record<'sort' | 'order' | 'search' | 'favorites', string> >; const defaultSort: SortingRule = { id: 'createdAt', desc: true }; const { value: storedParams, setValue: setStoredParams } = createLocalStorage( 'FeatureToggleListTable:v1', defaultSort, ); /** * @deprecated remove with flag `featureSearchFrontend` */ export const FeatureToggleListTable: VFC = () => { const theme = useTheme(); const { environments } = useEnvironments(); const enabledEnvironments = environments .filter((env) => env.enabled) .map((env) => env.name); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); const [showExportDialog, setShowExportDialog] = useState(false); const { features = [], loading, refetchFeatures } = useFeatures(); const [searchParams, setSearchParams] = useSearchParams(); const { setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); const [initialState] = useState(() => ({ sortBy: [ { id: searchParams.get('sort') || storedParams.id, desc: searchParams.has('order') ? searchParams.get('order') === 'desc' : storedParams.desc, }, ], hiddenColumns: ['description'], globalFilter: searchParams.get('search') || '', })); const { value: globalStore, setValue: setGlobalStore } = useGlobalLocalStorage(); const { isFavoritesPinned, sortTypes, onChangeIsFavoritePinned } = usePinnedFavorites( searchParams.has('favorites') ? searchParams.get('favorites') === 'true' : globalStore.favorites, ); const [searchValue, setSearchValue] = useState(initialState.globalFilter); const { favorite, unfavorite } = useFavoriteFeaturesApi(); const onFavorite = useCallback( async (feature: any) => { try { if (feature?.favorite) { await unfavorite(feature.project, feature.name); } else { await favorite(feature.project, feature.name); } refetchFeatures(); } catch (error) { setToastApiError( 'Something went wrong, could not update favorite', ); } }, [favorite, refetchFeatures, unfavorite, setToastApiError], ); const columns = useMemo( () => [ { Header: ( ), accessor: 'favorite', Cell: ({ row: { original: feature } }: any) => ( onFavorite(feature)} /> ), maxWidth: 50, disableSortBy: true, }, { Header: 'Seen', accessor: 'lastSeenAt', Cell: ({ value, row: { original: feature } }: any) => { return ; }, align: 'center', maxWidth: 80, }, { Header: 'Type', accessor: 'type', Cell: FeatureTypeCell, align: 'center', maxWidth: 85, }, { Header: 'Name', accessor: 'name', minWidth: 150, Cell: FeatureNameCell, sortType: 'alphanumeric', searchable: true, }, { id: 'tags', Header: 'Tags', accessor: (row: FeatureSchema) => row.tags ?.map(({ type, value }) => `${type}:${value}`) .join('\n') || '', Cell: FeatureTagCell, width: 80, searchable: true, }, { Header: 'Created', accessor: 'createdAt', Cell: DateCell, maxWidth: 150, }, { Header: 'Project ID', accessor: 'project', Cell: ({ value }: { value: string }) => ( ), sortType: 'alphanumeric', maxWidth: 150, filterName: 'project', searchable: true, }, { Header: 'State', accessor: 'stale', Cell: FeatureStaleCell, sortType: 'boolean', maxWidth: 120, filterName: 'state', filterParsing: (value: any) => (value ? 'stale' : 'active'), }, // Always hidden -- for search { accessor: 'description', Header: 'Description', searchable: true, }, ], [isFavoritesPinned], ); const { data: searchedData, getSearchText, getSearchContext, } = useSearch(columns, searchValue, features); const data = useMemo( () => searchedData?.length === 0 && loading ? featuresPlaceholder : searchedData, [searchedData, loading], ); const { headerGroups, rows, prepareRow, state: { sortBy }, setHiddenColumns, } = useTable( { columns: columns as any[], data, initialState, sortTypes, autoResetHiddenColumns: false, autoResetSortBy: false, disableSortRemove: true, disableMultiSort: true, }, useSortBy, useFlexLayout, ); useConditionallyHiddenColumns( [ { condition: !features.some(({ tags }) => tags?.length), columns: ['tags'], }, { condition: isSmallScreen, columns: ['type', 'createdAt', 'tags'], }, { condition: isMediumScreen, columns: ['lastSeenAt', 'stale'], }, ], setHiddenColumns, columns, ); useEffect(() => { const tableState: PageQueryType = {}; tableState.sort = sortBy[0].id; if (sortBy[0].desc) { tableState.order = 'desc'; } if (searchValue) { tableState.search = searchValue; } if (isFavoritesPinned) { tableState.favorites = 'true'; } setSearchParams(tableState, { replace: true, }); setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false, }); setGlobalStore((params) => ({ ...params, favorites: Boolean(isFavoritesPinned), })); }, [sortBy, searchValue, setSearchParams, isFavoritesPinned]); if (!(environments.length > 0)) { return null; } return ( } /> View archive setShowExportDialog(true) } sx={(theme) => ({ marginRight: theme.spacing(2), })} > } /> } > } /> } > 0} show={ No feature toggles found matching “ {searchValue} ” } elseShow={ No feature toggles available. Get started by adding a new feature toggle. } /> } /> setShowExportDialog(false)} environments={enabledEnvironments} /> } /> ); };