mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-27 11:02:16 +01:00
feat: improve flag filters on project page (#10705)
This commit is contained in:
parent
c12aca72db
commit
9d996f14d9
@ -37,7 +37,6 @@ interface ILifecycleFiltersBaseProps {
|
||||
const Wrapper = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
minHeight: theme.spacing(7),
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { useFeatureToggleSwitch } from '../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch.tsx';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx';
|
||||
import { ProjectFeatureTogglesHeader as LegacyProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/LegacyProjectFeatureTogglesHeader.tsx';
|
||||
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
|
||||
import { withTableState } from 'utils/withTableState';
|
||||
import type { FeatureSearchResponseSchema } from 'openapi';
|
||||
@ -41,7 +41,7 @@ import {
|
||||
useProjectFeatureSearchActions,
|
||||
} from './useProjectFeatureSearch.ts';
|
||||
import { AvatarCell } from './AvatarCell.tsx';
|
||||
import { styled } from '@mui/material';
|
||||
import { styled, useMediaQuery, useTheme } from '@mui/material';
|
||||
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||
import { ConnectSdkDialog } from '../../../onboarding/dialog/ConnectSdkDialog.tsx';
|
||||
import { ProjectOnboarding } from '../../../onboarding/flow/ProjectOnboarding.tsx';
|
||||
@ -57,6 +57,9 @@ import { IMPORT_BUTTON } from 'utils/testIds';
|
||||
import { ProjectCleanupReminder } from './ProjectCleanupReminder/ProjectCleanupReminder.tsx';
|
||||
import { formatEnvironmentColumnId } from './formatEnvironmentColumnId.ts';
|
||||
import { ProjectFeaturesColumnsMenu } from './ProjectFeaturesColumnsMenu/ProjectFeaturesColumnsMenu.tsx';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx';
|
||||
import { ProjectFlagsSearch } from './ProjectFlagsSearch/ProjectFlagsSearch.tsx';
|
||||
|
||||
type ProjectFeatureTogglesProps = {
|
||||
environments: string[];
|
||||
@ -71,12 +74,27 @@ const Container = styled('div')(({ theme }) => ({
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const FilterRow = styled('div')(({ theme }) => ({
|
||||
const LegacyFilterRow = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexFlow: 'row wrap',
|
||||
justifyContent: 'space-between',
|
||||
}));
|
||||
|
||||
const FiltersContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(1),
|
||||
padding: theme.spacing(2, 3, 2),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(2, 2),
|
||||
},
|
||||
}));
|
||||
|
||||
const FilterRow = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const ButtonGroup = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(1),
|
||||
@ -91,6 +109,9 @@ export const ProjectFeatureToggles = ({
|
||||
const { project } = useProjectOverview(projectId);
|
||||
const [connectSdkOpen, setConnectSdkOpen] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const flagsUiFilterRefactorEnabled = useUiFlag('flagsUiFilterRefactor');
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
const {
|
||||
features,
|
||||
@ -500,25 +521,32 @@ export const ProjectFeatureToggles = ({
|
||||
) : null}
|
||||
<PageContent
|
||||
disableLoading
|
||||
disablePadding
|
||||
header={
|
||||
<ProjectFeatureTogglesHeader
|
||||
isLoading={initialLoad}
|
||||
totalItems={total}
|
||||
searchQuery={tableState.query || ''}
|
||||
onChangeSearchQuery={(query) => {
|
||||
setTableState({ query });
|
||||
}}
|
||||
dataToExport={data}
|
||||
environmentsToExport={environments}
|
||||
actions={
|
||||
<ProjectFeaturesColumnsMenu
|
||||
columnVisibility={columnVisibility}
|
||||
environments={environments}
|
||||
onToggle={onToggleColumnVisibility}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
flagsUiFilterRefactorEnabled ? (
|
||||
<ProjectFeatureTogglesHeader
|
||||
isLoading={initialLoad}
|
||||
totalItems={total}
|
||||
environmentsToExport={environments}
|
||||
/>
|
||||
) : (
|
||||
<LegacyProjectFeatureTogglesHeader
|
||||
isLoading={initialLoad}
|
||||
totalItems={total}
|
||||
searchQuery={tableState.query || ''}
|
||||
onChangeSearchQuery={(query) => {
|
||||
setTableState({ query });
|
||||
}}
|
||||
dataToExport={data}
|
||||
environmentsToExport={environments}
|
||||
actions={
|
||||
<ProjectFeaturesColumnsMenu
|
||||
columnVisibility={columnVisibility}
|
||||
environments={environments}
|
||||
onToggle={onToggleColumnVisibility}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
bodyClass='noop'
|
||||
style={{ cursor: 'inherit' }}
|
||||
@ -528,31 +556,74 @@ export const ProjectFeatureToggles = ({
|
||||
aria-busy={isPlaceholder}
|
||||
aria-live='polite'
|
||||
>
|
||||
<FilterRow>
|
||||
<ProjectOverviewFilters
|
||||
project={projectId}
|
||||
onChange={setTableState}
|
||||
state={filterState}
|
||||
/>
|
||||
<ProjectLifecycleFilters
|
||||
projectId={projectId}
|
||||
state={filterState}
|
||||
onChange={setTableState}
|
||||
total={loading ? undefined : total}
|
||||
/>
|
||||
<ButtonGroup>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_FEATURE}
|
||||
{flagsUiFilterRefactorEnabled ? (
|
||||
<FiltersContainer>
|
||||
<FilterRow>
|
||||
<ProjectLifecycleFilters
|
||||
projectId={projectId}
|
||||
state={filterState}
|
||||
onChange={setTableState}
|
||||
total={loading ? undefined : total}
|
||||
/>
|
||||
{isSmallScreen ? null : (
|
||||
<ProjectFlagsSearch
|
||||
searchQuery={tableState.query || ''}
|
||||
onChangeSearchQuery={(query) => {
|
||||
setTableState({ query });
|
||||
}}
|
||||
isLoading={loading}
|
||||
/>
|
||||
)}
|
||||
<ProjectFeaturesColumnsMenu
|
||||
columnVisibility={columnVisibility}
|
||||
environments={environments}
|
||||
onToggle={onToggleColumnVisibility}
|
||||
/>
|
||||
</FilterRow>
|
||||
<FilterRow>
|
||||
<ProjectOverviewFilters
|
||||
project={projectId}
|
||||
onChange={setTableState}
|
||||
state={filterState}
|
||||
/>
|
||||
</FilterRow>
|
||||
{isSmallScreen ? (
|
||||
<ProjectFlagsSearch
|
||||
searchQuery={tableState.query || ''}
|
||||
onChangeSearchQuery={(query) => {
|
||||
setTableState({ query });
|
||||
}}
|
||||
isLoading={loading}
|
||||
/>
|
||||
) : null}
|
||||
</FiltersContainer>
|
||||
) : (
|
||||
<LegacyFilterRow>
|
||||
<ProjectOverviewFilters
|
||||
project={projectId}
|
||||
onChange={setTableState}
|
||||
state={filterState}
|
||||
/>
|
||||
<ProjectLifecycleFilters
|
||||
projectId={projectId}
|
||||
onClick={() => setModalOpen(true)}
|
||||
tooltipProps={{ title: 'Import' }}
|
||||
data-testid={IMPORT_BUTTON}
|
||||
data-loading-project
|
||||
>
|
||||
<ImportSvg />
|
||||
</PermissionIconButton>
|
||||
</ButtonGroup>
|
||||
</FilterRow>
|
||||
state={filterState}
|
||||
onChange={setTableState}
|
||||
total={loading ? undefined : total}
|
||||
/>
|
||||
<ButtonGroup>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
onClick={() => setModalOpen(true)}
|
||||
tooltipProps={{ title: 'Import' }}
|
||||
data-testid={IMPORT_BUTTON}
|
||||
data-loading-project
|
||||
>
|
||||
<ImportSvg />
|
||||
</PermissionIconButton>
|
||||
</ButtonGroup>
|
||||
</LegacyFilterRow>
|
||||
)}
|
||||
<SearchHighlightProvider value={tableState.query || ''}>
|
||||
<PaginatedTable
|
||||
tableInstance={table}
|
||||
|
||||
@ -0,0 +1,165 @@
|
||||
import { type ReactNode, type FC, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
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 { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
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 IosShare from '@mui/icons-material/IosShare';
|
||||
import { FlagCreationButton } from './FlagCreationButton/FlagCreationButton.tsx';
|
||||
|
||||
interface IProjectFeatureTogglesHeaderProps {
|
||||
isLoading?: boolean;
|
||||
totalItems?: number;
|
||||
searchQuery?: string;
|
||||
onChangeSearchQuery?: (query: string) => void;
|
||||
dataToExport?: Pick<FeatureSchema, 'name'>[];
|
||||
environmentsToExport?: string[];
|
||||
actions?: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated remove with `flagsUiFilterRefactor` flag
|
||||
*/
|
||||
export const ProjectFeatureTogglesHeader: FC<
|
||||
IProjectFeatureTogglesHeaderProps
|
||||
> = ({
|
||||
isLoading,
|
||||
totalItems,
|
||||
searchQuery,
|
||||
onChangeSearchQuery,
|
||||
environmentsToExport,
|
||||
actions,
|
||||
}) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const headerLoadingRef = useLoading(isLoading || false);
|
||||
const [showTitle, setShowTitle] = useState(true);
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const [showExportDialog, setShowExportDialog] = useState(false);
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
const projectOverviewRefactorFeedback = false;
|
||||
const { openFeedback } = useFeedback('newProjectOverview', 'automatic');
|
||||
const handleSearch = (query: string) => {
|
||||
onChangeSearchQuery?.(query);
|
||||
trackEvent('search-bar', {
|
||||
props: {
|
||||
screen: 'project',
|
||||
length: query.length,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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} aria-busy={isLoading} aria-live='polite'>
|
||||
<PageHeader
|
||||
titleElement={
|
||||
showTitle
|
||||
? `Feature flags ${
|
||||
totalItems !== undefined ? `(${totalItems})` : ''
|
||||
}`
|
||||
: null
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={!isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
data-loading
|
||||
placeholder='Search and Filter'
|
||||
expandable
|
||||
initialValue={searchQuery || ''}
|
||||
onChange={handleSearch}
|
||||
onFocus={() => setShowTitle(false)}
|
||||
onBlur={() => setShowTitle(true)}
|
||||
hasFilters
|
||||
id='projectFeatureFlags'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{actions}
|
||||
<PageHeader.Divider sx={{ marginLeft: 0 }} />
|
||||
<Tooltip title='Export all project flags' arrow>
|
||||
<IconButton
|
||||
data-loading
|
||||
onClick={() => setShowExportDialog(true)}
|
||||
sx={(theme) => ({
|
||||
marginRight: theme.spacing(2),
|
||||
})}
|
||||
>
|
||||
<IosShare />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={!isLoading}
|
||||
show={
|
||||
<ExportDialog
|
||||
showExportDialog={showExportDialog}
|
||||
project={projectId}
|
||||
data={[]}
|
||||
onClose={() => setShowExportDialog(false)}
|
||||
environments={environmentsToExport || []}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* FIXME: remove */}
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
projectOverviewRefactorFeedback &&
|
||||
!isSmallScreen
|
||||
}
|
||||
show={
|
||||
<Button
|
||||
startIcon={<ReviewsOutlined />}
|
||||
onClick={createFeedbackContext}
|
||||
variant='outlined'
|
||||
data-loading
|
||||
>
|
||||
Provide feedback
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<FlagCreationButton isLoading={isLoading} />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
initialValue={searchQuery || ''}
|
||||
onChange={handleSearch}
|
||||
hasFilters
|
||||
id='projectFeatureFlags'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</PageHeader>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -1,136 +1,59 @@
|
||||
import { type ReactNode, type FC, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { type FC, useState } from 'react';
|
||||
import { Box, IconButton, Tooltip } from '@mui/material';
|
||||
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 { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog';
|
||||
import type { FeatureSchema } from 'openapi';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import IosShare from '@mui/icons-material/IosShare';
|
||||
import { FlagCreationButton } from './FlagCreationButton/FlagCreationButton.tsx';
|
||||
import { ImportButton } from './ImportButton/ImportButton.tsx';
|
||||
|
||||
interface IProjectFeatureTogglesHeaderProps {
|
||||
type ProjectFeatureTogglesHeaderProps = {
|
||||
isLoading?: boolean;
|
||||
totalItems?: number;
|
||||
searchQuery?: string;
|
||||
onChangeSearchQuery?: (query: string) => void;
|
||||
dataToExport?: Pick<FeatureSchema, 'name'>[];
|
||||
environmentsToExport?: string[];
|
||||
actions?: ReactNode;
|
||||
}
|
||||
};
|
||||
|
||||
export const ProjectFeatureTogglesHeader: FC<
|
||||
IProjectFeatureTogglesHeaderProps
|
||||
> = ({
|
||||
isLoading,
|
||||
totalItems,
|
||||
searchQuery,
|
||||
onChangeSearchQuery,
|
||||
environmentsToExport,
|
||||
actions,
|
||||
}) => {
|
||||
ProjectFeatureTogglesHeaderProps
|
||||
> = ({ isLoading, totalItems, environmentsToExport }) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const headerLoadingRef = useLoading(isLoading || false);
|
||||
const [showTitle, setShowTitle] = useState(true);
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const [showExportDialog, setShowExportDialog] = useState(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'
|
||||
sx={(theme) => ({
|
||||
padding: `${theme.spacing(2.5)} ${theme.spacing(3.125)}`,
|
||||
})}
|
||||
>
|
||||
<Box ref={headerLoadingRef} aria-busy={isLoading} aria-live='polite'>
|
||||
<PageHeader
|
||||
titleElement={
|
||||
showTitle
|
||||
? `Feature flags ${
|
||||
totalItems !== undefined ? `(${totalItems})` : ''
|
||||
}`
|
||||
: null
|
||||
}
|
||||
titleElement={`Feature flags ${
|
||||
totalItems !== undefined ? `(${totalItems})` : ''
|
||||
}`}
|
||||
actions={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={!isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
data-loading
|
||||
placeholder='Search and Filter'
|
||||
expandable
|
||||
initialValue={searchQuery || ''}
|
||||
onChange={handleSearch}
|
||||
onFocus={() => setShowTitle(false)}
|
||||
onBlur={() => setShowTitle(true)}
|
||||
hasFilters
|
||||
id='projectFeatureFlags'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{actions}
|
||||
<PageHeader.Divider sx={{ marginLeft: 0 }} />
|
||||
<Tooltip title='Export all project flags' arrow>
|
||||
<IconButton
|
||||
data-loading
|
||||
onClick={() => setShowExportDialog(true)}
|
||||
sx={(theme) => ({
|
||||
marginRight: theme.spacing(2),
|
||||
})}
|
||||
>
|
||||
<IosShare />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<ImportButton />
|
||||
|
||||
<ConditionallyRender
|
||||
condition={!isLoading}
|
||||
show={
|
||||
<ExportDialog
|
||||
showExportDialog={showExportDialog}
|
||||
project={projectId}
|
||||
data={[]}
|
||||
onClose={() => setShowExportDialog(false)}
|
||||
environments={environmentsToExport || []}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<FlagCreationButton isLoading={isLoading} />
|
||||
{!isLoading ? (
|
||||
<ExportDialog
|
||||
showExportDialog={showExportDialog}
|
||||
project={projectId}
|
||||
data={[]}
|
||||
onClose={() => setShowExportDialog(false)}
|
||||
environments={environmentsToExport || []}
|
||||
/>
|
||||
) : null}
|
||||
<Box>
|
||||
<FlagCreationButton isLoading={isLoading} />
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
initialValue={searchQuery || ''}
|
||||
onChange={handleSearch}
|
||||
hasFilters
|
||||
id='projectFeatureFlags'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</PageHeader>
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { FC } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ColumnsMenu } from '../ColumnsMenu/ColumnsMenu.tsx';
|
||||
import { formatEnvironmentColumnId } from '../formatEnvironmentColumnId.ts';
|
||||
|
||||
@ -12,47 +13,49 @@ 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}
|
||||
/>
|
||||
<Box sx={(theme) => ({ marginLeft: theme.spacing(1) })}>
|
||||
<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}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import type { FilterItemParamHolder } from '../../../filter/Filters/Filters.tsx'
|
||||
import { useProjectStatus } from 'hooks/api/getters/useProjectStatus/useProjectStatus';
|
||||
import { LifecycleFilters } from 'component/common/LifecycleFilters/LifecycleFilters.tsx';
|
||||
import { Box, useMediaQuery, useTheme } from '@mui/material';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
|
||||
type ProjectLifecycleFiltersProps = {
|
||||
projectId: string;
|
||||
@ -23,6 +24,7 @@ export const ProjectLifecycleFilters: FC<ProjectLifecycleFiltersProps> = ({
|
||||
const { data } = useProjectStatus(projectId);
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const flagsUiFilterRefactorEnabled = useUiFlag('flagsUiFilterRefactor');
|
||||
const lifecycleSummary = Object.entries(
|
||||
data?.lifecycleSummary || {},
|
||||
).reduce(
|
||||
@ -48,7 +50,13 @@ export const ProjectLifecycleFilters: FC<ProjectLifecycleFiltersProps> = ({
|
||||
<Box
|
||||
sx={{
|
||||
marginRight: 'auto',
|
||||
margin: isSmallScreen ? theme.spacing(0, 2) : '0 auto 0 0',
|
||||
...(!flagsUiFilterRefactorEnabled
|
||||
? {
|
||||
margin: isSmallScreen
|
||||
? theme.spacing(0, 3)
|
||||
: `${theme.spacing(1.5)} auto 0 0`,
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
<LifecycleFilters
|
||||
|
||||
@ -7,6 +7,8 @@ import {
|
||||
} from 'component/filter/Filters/Filters';
|
||||
import { useProjectFlagCreators } from 'hooks/api/getters/useProjectFlagCreators/useProjectFlagCreators';
|
||||
import { formatTag } from 'utils/format-tag';
|
||||
import { styled } from '@mui/material';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
type ProjectOverviewFiltersProps = {
|
||||
state: FilterItemParamHolder;
|
||||
@ -14,6 +16,10 @@ type ProjectOverviewFiltersProps = {
|
||||
project: string;
|
||||
};
|
||||
|
||||
const StyledFilters = styled(Filters)({
|
||||
padding: 0,
|
||||
});
|
||||
|
||||
export const ProjectOverviewFilters: FC<ProjectOverviewFiltersProps> = ({
|
||||
state,
|
||||
onChange,
|
||||
@ -22,6 +28,10 @@ export const ProjectOverviewFilters: FC<ProjectOverviewFiltersProps> = ({
|
||||
const { tags } = useAllTags();
|
||||
const { flagCreators } = useProjectFlagCreators(project);
|
||||
const [availableFilters, setAvailableFilters] = useState<IFilterItem[]>([]);
|
||||
const flagsUiFilterRefactorEnabled = useUiFlag('flagsUiFilterRefactor');
|
||||
const FilterComponent = flagsUiFilterRefactorEnabled
|
||||
? StyledFilters
|
||||
: Filters;
|
||||
|
||||
useEffect(() => {
|
||||
const tagsOptions = (tags || []).map((tag) => {
|
||||
@ -124,7 +134,7 @@ export const ProjectOverviewFilters: FC<ProjectOverviewFiltersProps> = ({
|
||||
}, [JSON.stringify(tags), JSON.stringify(flagCreators)]);
|
||||
|
||||
return (
|
||||
<Filters
|
||||
<FilterComponent
|
||||
availableFilters={availableFilters}
|
||||
state={state}
|
||||
onChange={onChange}
|
||||
|
||||
@ -88,6 +88,7 @@ export type UiFlags = {
|
||||
lifecycleGraphs?: boolean;
|
||||
newStrategyModal?: boolean;
|
||||
globalChangeRequestList?: boolean;
|
||||
flagsUiFilterRefactor?: boolean;
|
||||
};
|
||||
|
||||
export interface IVersionInfo {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user