mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Refactor flag filters (#10703)
Refactored and simplified code around flag filters, in preparation for UI improvements. It's split into 2 PRs in order to simplify what needs to be behind a flag and what doesn't. - `ExperimentalColumnsMenu` moved to `ColumnsMenu`, old unused `ColumnsMenu` removed - Parts of the code moved to `ProjectFeaturesColumnsMenu` - Moved `FlagCreationButton` to a separate file - Removed part behind archived flag (`projectOverviewRefactorFeedback`)
This commit is contained in:
		
							parent
							
								
									e46f8881d1
								
							
						
					
					
						commit
						c12aca72db
					
				| @ -4,7 +4,7 @@ import { | ||||
|     UPDATE_PROJECT, | ||||
|     CREATE_PROJECT_API_TOKEN, | ||||
| } from 'component/providers/AccessProvider/permissions'; | ||||
| import { FlagCreationButton } from '../../project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx'; | ||||
| import { FlagCreationButton } from '../../project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/FlagCreationButton/FlagCreationButton.tsx'; | ||||
| import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; | ||||
| import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; | ||||
| import { SdkExample } from './SdkExample.tsx'; | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { useState, type VFC } from 'react'; | ||||
| import { useState, type FC } from 'react'; | ||||
| import { | ||||
|     IconButton, | ||||
|     ListItemIcon, | ||||
| @ -17,7 +17,7 @@ import { | ||||
|     StyledDivider, | ||||
|     StyledIconButton, | ||||
|     StyledMenuItem, | ||||
| } from './ExperimentalColumnsMenu.styles'; | ||||
| } from './ColumnsMenu.styles'; | ||||
| 
 | ||||
| interface IColumnsMenuProps { | ||||
|     columns: { | ||||
| @ -29,10 +29,7 @@ interface IColumnsMenuProps { | ||||
|     onToggle?: (id: string) => void; | ||||
| } | ||||
| 
 | ||||
| export const ExperimentalColumnsMenu: VFC<IColumnsMenuProps> = ({ | ||||
|     columns, | ||||
|     onToggle, | ||||
| }) => { | ||||
| export const ColumnsMenu: FC<IColumnsMenuProps> = ({ columns, onToggle }) => { | ||||
|     const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | ||||
| 
 | ||||
|     const onIconClick = (event: React.MouseEvent<HTMLButtonElement>) => { | ||||
| @ -1,41 +0,0 @@ | ||||
| import { | ||||
|     Box, | ||||
|     Checkbox, | ||||
|     Divider, | ||||
|     IconButton, | ||||
|     MenuItem, | ||||
|     styled, | ||||
| } from '@mui/material'; | ||||
| 
 | ||||
| import { flexRow } from 'themes/themeStyles'; | ||||
| 
 | ||||
| export const StyledBoxContainer = styled(Box)(() => ({ | ||||
|     ...flexRow, | ||||
|     justifyContent: 'center', | ||||
| })); | ||||
| 
 | ||||
| export const StyledIconButton = styled(IconButton)(({ theme }) => ({ | ||||
|     margin: theme.spacing(-1, 0), | ||||
| })); | ||||
| 
 | ||||
| export const StyledBoxMenuHeader = styled(Box)(({ theme }) => ({ | ||||
|     ...flexRow, | ||||
|     justifyContent: 'space-between', | ||||
|     padding: theme.spacing(1, 1, 0, 4), | ||||
| })); | ||||
| 
 | ||||
| export const StyledMenuItem = styled(MenuItem)(({ theme }) => ({ | ||||
|     padding: theme.spacing(0, 2), | ||||
|     margin: theme.spacing(0, 2), | ||||
|     borderRadius: theme.shape.borderRadius, | ||||
| })); | ||||
| 
 | ||||
| export const StyledDivider = styled(Divider)(({ theme }) => ({ | ||||
|     '&.MuiDivider-root.MuiDivider-fullWidth': { | ||||
|         margin: theme.spacing(0.75, 0), | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| export const StyledCheckbox = styled(Checkbox)(({ theme }) => ({ | ||||
|     padding: theme.spacing(0.75, 1), | ||||
| })); | ||||
| @ -9,7 +9,6 @@ import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightC | ||||
| import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader'; | ||||
| import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell'; | ||||
| import { ActionsCell } from '../ProjectFeatureToggles/ActionsCell/ActionsCell.tsx'; | ||||
| import { ExperimentalColumnsMenu as ColumnsMenu } from './ExperimentalColumnsMenu/ExperimentalColumnsMenu.tsx'; | ||||
| import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi'; | ||||
| import { MemoizedRowSelectCell } from '../ProjectFeatureToggles/RowSelectCell/RowSelectCell.tsx'; | ||||
| import { BatchSelectionActionsBar } from 'component/common/BatchSelectionActionsBar/BatchSelectionActionsBar'; | ||||
| @ -56,14 +55,13 @@ import { UPDATE_FEATURE } from '@server/types/permissions'; | ||||
| import { ImportModal } from '../Import/ImportModal.tsx'; | ||||
| import { IMPORT_BUTTON } from 'utils/testIds'; | ||||
| import { ProjectCleanupReminder } from './ProjectCleanupReminder/ProjectCleanupReminder.tsx'; | ||||
| import { formatEnvironmentColumnId } from './formatEnvironmentColumnId.ts'; | ||||
| import { ProjectFeaturesColumnsMenu } from './ProjectFeaturesColumnsMenu/ProjectFeaturesColumnsMenu.tsx'; | ||||
| 
 | ||||
| type ProjectFeatureTogglesProps = { | ||||
|     environments: string[]; | ||||
| }; | ||||
| 
 | ||||
| const formatEnvironmentColumnId = (environment: string) => | ||||
|     `environment:${environment}`; | ||||
| 
 | ||||
| const columnHelper = createColumnHelper<FeatureSearchResponseSchema>(); | ||||
| const getRowId = (row: { name: string }) => row.name; | ||||
| 
 | ||||
| @ -514,50 +512,9 @@ export const ProjectFeatureToggles = ({ | ||||
|                         dataToExport={data} | ||||
|                         environmentsToExport={environments} | ||||
|                         actions={ | ||||
|                             <ColumnsMenu | ||||
|                                 columns={[ | ||||
|                                     { | ||||
|                                         header: 'Name', | ||||
|                                         id: 'name', | ||||
|                                         isVisible: columnVisibility.name, | ||||
|                                         isStatic: true, | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         header: 'Created', | ||||
|                                         id: 'createdAt', | ||||
|                                         isVisible: columnVisibility.createdAt, | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         header: 'By', | ||||
|                                         id: 'createdBy', | ||||
|                                         isVisible: columnVisibility.createdBy, | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         header: 'Last seen', | ||||
|                                         id: 'lastSeenAt', | ||||
|                                         isVisible: columnVisibility.lastSeenAt, | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         header: 'Lifecycle', | ||||
|                                         id: 'lifecycle', | ||||
|                                         isVisible: columnVisibility.lifecycle, | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         id: 'divider', | ||||
|                                     }, | ||||
|                                     ...environments.map((environment) => ({ | ||||
|                                         header: environment, | ||||
|                                         id: formatEnvironmentColumnId( | ||||
|                                             environment, | ||||
|                                         ), | ||||
|                                         isVisible: | ||||
|                                             columnVisibility[ | ||||
|                                                 formatEnvironmentColumnId( | ||||
|                                                     environment, | ||||
|                                                 ) | ||||
|                                             ], | ||||
|                                     })), | ||||
|                                 ]} | ||||
|                             <ProjectFeaturesColumnsMenu | ||||
|                                 columnVisibility={columnVisibility} | ||||
|                                 environments={environments} | ||||
|                                 onToggle={onToggleColumnVisibility} | ||||
|                             /> | ||||
|                         } | ||||
|  | ||||
| @ -0,0 +1,66 @@ | ||||
| import { useState } from 'react'; | ||||
| import Add from '@mui/icons-material/Add'; | ||||
| import { styled } from '@mui/material'; | ||||
| import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; | ||||
| import { useSearchParams } from 'react-router-dom'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { CreateFeatureDialog } from '../CreateFeatureDialog.tsx'; | ||||
| import type { OverridableStringUnion } from '@mui/types'; | ||||
| import type { ButtonPropsVariantOverrides } from '@mui/material/Button/Button'; | ||||
| import { NAVIGATE_TO_CREATE_FEATURE } from 'utils/testIds'; | ||||
| 
 | ||||
| interface IFlagCreationButtonProps { | ||||
|     text?: string; | ||||
|     variant?: OverridableStringUnion< | ||||
|         'text' | 'outlined' | 'contained', | ||||
|         ButtonPropsVariantOverrides | ||||
|     >; | ||||
|     skipNavigationOnComplete?: boolean; | ||||
|     isLoading?: boolean; | ||||
|     onSuccess?: () => void; | ||||
| } | ||||
| 
 | ||||
| const StyledResponsiveButton = styled(ResponsiveButton)(() => ({ | ||||
|     whiteSpace: 'nowrap', | ||||
| })); | ||||
| 
 | ||||
| export const FlagCreationButton = ({ | ||||
|     variant, | ||||
|     text = 'New feature flag', | ||||
|     skipNavigationOnComplete, | ||||
|     isLoading, | ||||
|     onSuccess, | ||||
| }: IFlagCreationButtonProps) => { | ||||
|     const { loading } = useUiConfig(); | ||||
|     const [searchParams] = useSearchParams(); | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
|     const showCreateDialog = Boolean(searchParams.get('create')); | ||||
|     const [openCreateDialog, setOpenCreateDialog] = useState(showCreateDialog); | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <StyledResponsiveButton | ||||
|                 onClick={() => setOpenCreateDialog(true)} | ||||
|                 maxWidth='960px' | ||||
|                 Icon={Add} | ||||
|                 projectId={projectId} | ||||
|                 disabled={loading || isLoading} | ||||
|                 variant={variant} | ||||
|                 permission={CREATE_FEATURE} | ||||
|                 data-testid={ | ||||
|                     loading || isLoading ? '' : NAVIGATE_TO_CREATE_FEATURE | ||||
|                 } | ||||
|             > | ||||
|                 {text} | ||||
|             </StyledResponsiveButton> | ||||
|             <CreateFeatureDialog | ||||
|                 open={openCreateDialog} | ||||
|                 onClose={() => setOpenCreateDialog(false)} | ||||
|                 skipNavigationOnComplete={skipNavigationOnComplete} | ||||
|                 onSuccess={onSuccess} | ||||
|             /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,35 @@ | ||||
| import { type FC, useState } from 'react'; | ||||
| import { ReactComponent as ImportSvg } from 'assets/icons/import.svg'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; | ||||
| import { UPDATE_FEATURE } from '@server/types/permissions'; | ||||
| 
 | ||||
| import { ImportModal } from '../../../Import/ImportModal.tsx'; | ||||
| import { IMPORT_BUTTON } from 'utils/testIds'; | ||||
| 
 | ||||
| type ImportButtonProps = {}; | ||||
| 
 | ||||
| export const ImportButton: FC<ImportButtonProps> = () => { | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
|     const [modalOpen, setModalOpen] = useState(false); | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <PermissionIconButton | ||||
|                 permission={UPDATE_FEATURE} | ||||
|                 projectId={projectId} | ||||
|                 onClick={() => setModalOpen(true)} | ||||
|                 tooltipProps={{ title: 'Import' }} | ||||
|                 data-testid={IMPORT_BUTTON} | ||||
|                 data-loading-project | ||||
|             > | ||||
|                 <ImportSvg /> | ||||
|             </PermissionIconButton> | ||||
|             <ImportModal | ||||
|                 open={modalOpen} | ||||
|                 setOpen={setModalOpen} | ||||
|                 project={projectId} | ||||
|             /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| @ -1,7 +1,6 @@ | ||||
| import { type ReactNode, type FC, useState } from 'react'; | ||||
| import { | ||||
|     Box, | ||||
|     Button, | ||||
|     IconButton, | ||||
|     Tooltip, | ||||
|     useMediaQuery, | ||||
| @ -11,24 +10,12 @@ import useLoading from 'hooks/useLoading'; | ||||
| import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Search } from 'component/common/Search/Search'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| import Add from '@mui/icons-material/Add'; | ||||
| import { styled } from '@mui/material'; | ||||
| import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; | ||||
| import { useSearchParams } from 'react-router-dom'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions'; | ||||
| import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog'; | ||||
| import type { FeatureSchema } from 'openapi'; | ||||
| import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; | ||||
| import ReviewsOutlined from '@mui/icons-material/ReviewsOutlined'; | ||||
| import { useFeedback } from 'component/feedbackNew/useFeedback'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { CreateFeatureDialog } from './CreateFeatureDialog.tsx'; | ||||
| import IosShare from '@mui/icons-material/IosShare'; | ||||
| import type { OverridableStringUnion } from '@mui/types'; | ||||
| import type { ButtonPropsVariantOverrides } from '@mui/material/Button/Button'; | ||||
| import { NAVIGATE_TO_CREATE_FEATURE } from 'utils/testIds'; | ||||
| import { FlagCreationButton } from './FlagCreationButton/FlagCreationButton.tsx'; | ||||
| 
 | ||||
| interface IProjectFeatureTogglesHeaderProps { | ||||
|     isLoading?: boolean; | ||||
| @ -40,60 +27,6 @@ interface IProjectFeatureTogglesHeaderProps { | ||||
|     actions?: ReactNode; | ||||
| } | ||||
| 
 | ||||
| interface IFlagCreationButtonProps { | ||||
|     text?: string; | ||||
|     variant?: OverridableStringUnion< | ||||
|         'text' | 'outlined' | 'contained', | ||||
|         ButtonPropsVariantOverrides | ||||
|     >; | ||||
|     skipNavigationOnComplete?: boolean; | ||||
|     isLoading?: boolean; | ||||
|     onSuccess?: () => void; | ||||
| } | ||||
| 
 | ||||
| const StyledResponsiveButton = styled(ResponsiveButton)(() => ({ | ||||
|     whiteSpace: 'nowrap', | ||||
| })); | ||||
| 
 | ||||
| export const FlagCreationButton = ({ | ||||
|     variant, | ||||
|     text = 'New feature flag', | ||||
|     skipNavigationOnComplete, | ||||
|     isLoading, | ||||
|     onSuccess, | ||||
| }: IFlagCreationButtonProps) => { | ||||
|     const { loading } = useUiConfig(); | ||||
|     const [searchParams] = useSearchParams(); | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
|     const showCreateDialog = Boolean(searchParams.get('create')); | ||||
|     const [openCreateDialog, setOpenCreateDialog] = useState(showCreateDialog); | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <StyledResponsiveButton | ||||
|                 onClick={() => setOpenCreateDialog(true)} | ||||
|                 maxWidth='960px' | ||||
|                 Icon={Add} | ||||
|                 projectId={projectId} | ||||
|                 disabled={loading || isLoading} | ||||
|                 variant={variant} | ||||
|                 permission={CREATE_FEATURE} | ||||
|                 data-testid={ | ||||
|                     loading || isLoading ? '' : NAVIGATE_TO_CREATE_FEATURE | ||||
|                 } | ||||
|             > | ||||
|                 {text} | ||||
|             </StyledResponsiveButton> | ||||
|             <CreateFeatureDialog | ||||
|                 open={openCreateDialog} | ||||
|                 onClose={() => setOpenCreateDialog(false)} | ||||
|                 skipNavigationOnComplete={skipNavigationOnComplete} | ||||
|                 onSuccess={onSuccess} | ||||
|             /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export const ProjectFeatureTogglesHeader: FC< | ||||
|     IProjectFeatureTogglesHeaderProps | ||||
| > = ({ | ||||
| @ -111,10 +44,6 @@ export const ProjectFeatureTogglesHeader: FC< | ||||
|     const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); | ||||
|     const [showExportDialog, setShowExportDialog] = useState(false); | ||||
|     const { trackEvent } = usePlausibleTracker(); | ||||
|     const projectOverviewRefactorFeedback = useUiFlag( | ||||
|         'projectOverviewRefactorFeedback', | ||||
|     ); | ||||
|     const { openFeedback } = useFeedback('newProjectOverview', 'automatic'); | ||||
|     const handleSearch = (query: string) => { | ||||
|         onChangeSearchQuery?.(query); | ||||
|         trackEvent('search-bar', { | ||||
| @ -125,16 +54,6 @@ export const ProjectFeatureTogglesHeader: FC< | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     const createFeedbackContext = () => { | ||||
|         openFeedback({ | ||||
|             title: 'How easy was it to work with the project overview in Unleash?', | ||||
|             positiveLabel: | ||||
|                 'What do you like most about the updated project overview?', | ||||
|             areasForImprovementsLabel: | ||||
|                 'What improvements are needed in the project overview?', | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <Box | ||||
|             ref={headerLoadingRef} | ||||
| @ -196,22 +115,6 @@ export const ProjectFeatureTogglesHeader: FC< | ||||
|                                 /> | ||||
|                             } | ||||
|                         /> | ||||
|                         <ConditionallyRender | ||||
|                             condition={ | ||||
|                                 projectOverviewRefactorFeedback && | ||||
|                                 !isSmallScreen | ||||
|                             } | ||||
|                             show={ | ||||
|                                 <Button | ||||
|                                     startIcon={<ReviewsOutlined />} | ||||
|                                     onClick={createFeedbackContext} | ||||
|                                     variant='outlined' | ||||
|                                     data-loading | ||||
|                                 > | ||||
|                                     Provide feedback | ||||
|                                 </Button> | ||||
|                             } | ||||
|                         /> | ||||
|                         <FlagCreationButton isLoading={isLoading} /> | ||||
|                     </> | ||||
|                 } | ||||
|  | ||||
| @ -0,0 +1,58 @@ | ||||
| import type { FC } from 'react'; | ||||
| import { ColumnsMenu } from '../ColumnsMenu/ColumnsMenu.tsx'; | ||||
| import { formatEnvironmentColumnId } from '../formatEnvironmentColumnId.ts'; | ||||
| 
 | ||||
| type ProjectFeaturesColumnsMenuProps = { | ||||
|     columnVisibility: Record<string, boolean>; | ||||
|     environments: string[]; | ||||
|     onToggle: (id: string) => void; | ||||
| }; | ||||
| 
 | ||||
| export const ProjectFeaturesColumnsMenu: FC< | ||||
|     ProjectFeaturesColumnsMenuProps | ||||
| > = ({ columnVisibility, environments, onToggle }) => { | ||||
|     return ( | ||||
|         <ColumnsMenu | ||||
|             columns={[ | ||||
|                 { | ||||
|                     header: 'Name', | ||||
|                     id: 'name', | ||||
|                     isVisible: columnVisibility.name, | ||||
|                     isStatic: true, | ||||
|                 }, | ||||
|                 { | ||||
|                     header: 'Created', | ||||
|                     id: 'createdAt', | ||||
|                     isVisible: columnVisibility.createdAt, | ||||
|                 }, | ||||
|                 { | ||||
|                     header: 'By', | ||||
|                     id: 'createdBy', | ||||
|                     isVisible: columnVisibility.createdBy, | ||||
|                 }, | ||||
|                 { | ||||
|                     header: 'Last seen', | ||||
|                     id: 'lastSeenAt', | ||||
|                     isVisible: columnVisibility.lastSeenAt, | ||||
|                 }, | ||||
|                 { | ||||
|                     header: 'Lifecycle', | ||||
|                     id: 'lifecycle', | ||||
|                     isVisible: columnVisibility.lifecycle, | ||||
|                 }, | ||||
|                 { | ||||
|                     id: 'divider', | ||||
|                 }, | ||||
|                 ...environments.map((environment) => ({ | ||||
|                     header: environment, | ||||
|                     id: formatEnvironmentColumnId(environment), | ||||
|                     isVisible: | ||||
|                         columnVisibility[ | ||||
|                             formatEnvironmentColumnId(environment) | ||||
|                         ], | ||||
|                 })), | ||||
|             ]} | ||||
|             onToggle={onToggle} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,42 @@ | ||||
| import type { FC } from 'react'; | ||||
| import { Box } from '@mui/material'; | ||||
| import { Search } from 'component/common/Search/Search'; | ||||
| import useLoading from 'hooks/useLoading'; | ||||
| import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; | ||||
| 
 | ||||
| interface IProjectFlagsSearchProps { | ||||
|     isLoading?: boolean; | ||||
|     searchQuery?: string; | ||||
|     onChangeSearchQuery?: (query: string) => void; | ||||
| } | ||||
| 
 | ||||
| export const ProjectFlagsSearch: FC<IProjectFlagsSearchProps> = ({ | ||||
|     isLoading, | ||||
|     searchQuery, | ||||
|     onChangeSearchQuery, | ||||
| }) => { | ||||
|     const headerLoadingRef = useLoading(isLoading || false); | ||||
|     const { trackEvent } = usePlausibleTracker(); | ||||
|     const handleSearch = (query: string) => { | ||||
|         onChangeSearchQuery?.(query); | ||||
|         trackEvent('search-bar', { | ||||
|             props: { | ||||
|                 screen: 'project', | ||||
|                 length: query.length, | ||||
|             }, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <Box ref={headerLoadingRef} aria-busy={isLoading} aria-live='polite'> | ||||
|             <Search | ||||
|                 placeholder='Search' | ||||
|                 expandable | ||||
|                 initialValue={searchQuery || ''} | ||||
|                 onChange={handleSearch} | ||||
|                 hasFilters | ||||
|                 id='projectFeatureFlags' | ||||
|             /> | ||||
|         </Box> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,2 @@ | ||||
| export const formatEnvironmentColumnId = (environment: string) => | ||||
|     `environment:${environment}`; | ||||
| @ -1,212 +0,0 @@ | ||||
| import { useEffect, useState, type VFC } from 'react'; | ||||
| import { | ||||
|     IconButton, | ||||
|     ListItemIcon, | ||||
|     ListItemText, | ||||
|     MenuList, | ||||
|     Popover, | ||||
|     Tooltip, | ||||
|     Typography, | ||||
|     useMediaQuery, | ||||
|     useTheme, | ||||
| } from '@mui/material'; | ||||
| import ColumnIcon from '@mui/icons-material/ViewWeek'; | ||||
| import CloseIcon from '@mui/icons-material/Close'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { | ||||
|     StyledBoxContainer, | ||||
|     StyledBoxMenuHeader, | ||||
|     StyledCheckbox, | ||||
|     StyledDivider, | ||||
|     StyledIconButton, | ||||
|     StyledMenuItem, | ||||
| } from './ColumnsMenu.styles'; | ||||
| 
 | ||||
| interface IColumnsMenuProps { | ||||
|     allColumns: { | ||||
|         Header?: string | any; | ||||
|         id: string; | ||||
|         isVisible: boolean; | ||||
|         toggleHidden: (state: boolean) => void; | ||||
|         hideInMenu?: boolean; | ||||
|     }[]; | ||||
|     staticColumns?: string[]; | ||||
|     dividerBefore?: string[]; | ||||
|     dividerAfter?: string[]; | ||||
|     isCustomized?: boolean; | ||||
|     setHiddenColumns: (hiddenColumns: string[]) => void; | ||||
|     onCustomize?: () => void; | ||||
| } | ||||
| 
 | ||||
| const columnNameMap: Record<string, string> = { | ||||
|     favorite: 'Favorite', | ||||
| }; | ||||
| 
 | ||||
| export const ColumnsMenu: VFC<IColumnsMenuProps> = ({ | ||||
|     allColumns, | ||||
|     staticColumns = [], | ||||
|     dividerBefore = [], | ||||
|     dividerAfter = [], | ||||
|     isCustomized = false, | ||||
|     onCustomize, | ||||
|     setHiddenColumns, | ||||
| }) => { | ||||
|     const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | ||||
|     const theme = useTheme(); | ||||
|     const isTinyScreen = useMediaQuery(theme.breakpoints.down('sm')); | ||||
|     const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); | ||||
|     const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (isCustomized) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const setVisibleColumns = ( | ||||
|             columns: string[], | ||||
|             environmentsToShow: number = 0, | ||||
|         ) => { | ||||
|             const visibleEnvColumns = allColumns | ||||
|                 .filter(({ id }) => id.startsWith('environment:') !== false) | ||||
|                 .map(({ id }) => id) | ||||
|                 .slice(0, environmentsToShow); | ||||
|             const hiddenColumns = allColumns | ||||
|                 .map(({ id }) => id) | ||||
|                 .filter((id) => !columns.includes(id)) | ||||
|                 .filter((id) => !staticColumns.includes(id)) | ||||
|                 .filter((id) => !visibleEnvColumns.includes(id)); | ||||
|             setHiddenColumns(hiddenColumns); | ||||
|         }; | ||||
| 
 | ||||
|         if (isTinyScreen) { | ||||
|             return setVisibleColumns(['createdAt']); | ||||
|         } | ||||
|         if (isSmallScreen) { | ||||
|             return setVisibleColumns(['createdAt'], 1); | ||||
|         } | ||||
|         if (isMediumScreen) { | ||||
|             return setVisibleColumns(['type', 'createdAt'], 1); | ||||
|         } | ||||
|         setVisibleColumns(['lastSeenAt', 'type', 'createdAt'], 3); | ||||
|         // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     }, [isTinyScreen, isSmallScreen, isMediumScreen]); | ||||
| 
 | ||||
|     const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { | ||||
|         setAnchorEl(event.currentTarget); | ||||
|     }; | ||||
| 
 | ||||
|     const handleClose = () => { | ||||
|         setAnchorEl(null); | ||||
|     }; | ||||
| 
 | ||||
|     const isOpen = Boolean(anchorEl); | ||||
|     const id = `columns-menu`; | ||||
|     const menuId = `columns-menu-list-${id}`; | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledBoxContainer> | ||||
|             <Tooltip title='Select columns' arrow describeChild> | ||||
|                 <StyledIconButton | ||||
|                     id={id} | ||||
|                     aria-controls={isOpen ? menuId : undefined} | ||||
|                     aria-haspopup='true' | ||||
|                     aria-expanded={isOpen ? 'true' : undefined} | ||||
|                     onClick={handleClick} | ||||
|                     type='button' | ||||
|                     size='large' | ||||
|                     data-loading | ||||
|                 > | ||||
|                     <ColumnIcon /> | ||||
|                 </StyledIconButton> | ||||
|             </Tooltip> | ||||
| 
 | ||||
|             <Popover | ||||
|                 id={menuId} | ||||
|                 open={isOpen} | ||||
|                 anchorEl={anchorEl} | ||||
|                 onClose={handleClose} | ||||
|                 anchorOrigin={{ | ||||
|                     vertical: 'top', | ||||
|                     horizontal: 'right', | ||||
|                 }} | ||||
|                 transformOrigin={{ | ||||
|                     vertical: 'top', | ||||
|                     horizontal: 'right', | ||||
|                 }} | ||||
|                 disableScrollLock={true} | ||||
|                 PaperProps={{ | ||||
|                     sx: (theme) => ({ | ||||
|                         borderRadius: theme.shape.borderRadius, | ||||
|                         paddingBottom: theme.spacing(2), | ||||
|                     }), | ||||
|                 }} | ||||
|             > | ||||
|                 <StyledBoxMenuHeader> | ||||
|                     <Typography variant='body2'> | ||||
|                         <strong>Columns</strong> | ||||
|                     </Typography> | ||||
|                     <IconButton onClick={handleClose}> | ||||
|                         <CloseIcon /> | ||||
|                     </IconButton> | ||||
|                 </StyledBoxMenuHeader> | ||||
|                 <MenuList> | ||||
|                     {allColumns | ||||
|                         .filter(({ hideInMenu }) => !hideInMenu) | ||||
|                         .map((column) => [ | ||||
|                             <ConditionallyRender | ||||
|                                 condition={dividerBefore.includes(column.id)} | ||||
|                                 show={<StyledDivider />} | ||||
|                             />, | ||||
|                             <StyledMenuItem | ||||
|                                 onClick={() => { | ||||
|                                     column.toggleHidden(column.isVisible); | ||||
|                                     onCustomize?.(); | ||||
|                                 }} | ||||
|                                 disabled={staticColumns.includes(column.id)} | ||||
|                             > | ||||
|                                 <ListItemIcon> | ||||
|                                     <StyledCheckbox | ||||
|                                         edge='start' | ||||
|                                         checked={column.isVisible} | ||||
|                                         disableRipple | ||||
|                                         inputProps={{ | ||||
|                                             'aria-labelledby': column.id, | ||||
|                                         }} | ||||
|                                         size='medium' | ||||
|                                     /> | ||||
|                                 </ListItemIcon> | ||||
|                                 <ListItemText | ||||
|                                     id={column.id} | ||||
|                                     primary={ | ||||
|                                         <Typography variant='body2'> | ||||
|                                             <ConditionallyRender | ||||
|                                                 condition={Boolean( | ||||
|                                                     typeof column.Header === | ||||
|                                                         'string' && | ||||
|                                                         column.Header, | ||||
|                                                 )} | ||||
|                                                 show={() => ( | ||||
|                                                     <>{column.Header}</> | ||||
|                                                 )} | ||||
|                                                 elseShow={() => ( | ||||
|                                                     <> | ||||
|                                                         {columnNameMap[ | ||||
|                                                             column.id | ||||
|                                                         ] || column.id} | ||||
|                                                     </> | ||||
|                                                 )} | ||||
|                                             /> | ||||
|                                         </Typography> | ||||
|                                     } | ||||
|                                 /> | ||||
|                             </StyledMenuItem>, | ||||
|                             <ConditionallyRender | ||||
|                                 condition={dividerAfter.includes(column.id)} | ||||
|                                 show={<StyledDivider />} | ||||
|                             />, | ||||
|                         ])} | ||||
|                 </MenuList> | ||||
|             </Popover> | ||||
|         </StyledBoxContainer> | ||||
|     ); | ||||
| }; | ||||
| @ -74,7 +74,6 @@ export type UiFlags = { | ||||
|     outdatedSdksBanner?: boolean; | ||||
|     estimateTrafficDataCost?: boolean; | ||||
|     disableShowContextFieldSelectionValues?: boolean; | ||||
|     projectOverviewRefactorFeedback?: boolean; | ||||
|     featureLifecycle?: boolean; | ||||
|     manyStrategiesPagination?: boolean; | ||||
|     enableLegacyVariants?: boolean; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user