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