mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: show and hide environments (#9323)
- Button to show and hide environments - Refactored hook storing state of hidden environments - Changed the way flag is triggered for feature overview - Visual updates for new page look --------- Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
		
							parent
							
								
									c16f7208a1
								
							
						
					
					
						commit
						2a6487e7e9
					
				@ -1,5 +1,4 @@
 | 
				
			|||||||
import NewFeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMetaData';
 | 
					import FeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMetaData';
 | 
				
			||||||
import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
 | 
					 | 
				
			||||||
import { Route, Routes, useNavigate } from 'react-router-dom';
 | 
					import { Route, Routes, useNavigate } from 'react-router-dom';
 | 
				
			||||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
 | 
					import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -8,21 +7,20 @@ import {
 | 
				
			|||||||
} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
 | 
					} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
 | 
				
			||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
					import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
				
			||||||
import { usePageTitle } from 'hooks/usePageTitle';
 | 
					import { usePageTitle } from 'hooks/usePageTitle';
 | 
				
			||||||
import { FeatureOverviewSidePanel as NewFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel';
 | 
					 | 
				
			||||||
import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments';
 | 
					 | 
				
			||||||
import { styled } from '@mui/material';
 | 
					import { styled } from '@mui/material';
 | 
				
			||||||
import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
 | 
					import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
 | 
				
			||||||
import { useEffect, useState } from 'react';
 | 
					import { useEffect } from 'react';
 | 
				
			||||||
import { useLastViewedFlags } from 'hooks/useLastViewedFlags';
 | 
					import { useLastViewedFlags } from 'hooks/useLastViewedFlags';
 | 
				
			||||||
import { useUiFlag } from 'hooks/useUiFlag';
 | 
					import { useUiFlag } from 'hooks/useUiFlag';
 | 
				
			||||||
import OldFeatureOverviewMetaData from './FeatureOverviewMetaData/OldFeatureOverviewMetaData';
 | 
					import { FeatureOverviewEnvironment } from './NewFeatureOverviewEnvironment/NewFeatureOverviewEnvironment';
 | 
				
			||||||
import { OldFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel';
 | 
					import { default as LegacyFleatureOverview } from './LegacyFeatureOverview';
 | 
				
			||||||
import { NewFeatureOverviewEnvironment } from './NewFeatureOverviewEnvironment/NewFeatureOverviewEnvironment';
 | 
					import { useEnvironmentVisibility } from './FeatureOverviewMetaData/EnvironmentVisibilityMenu/hooks/useEnvironmentVisibility';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const StyledContainer = styled('div')(({ theme }) => ({
 | 
					const StyledContainer = styled('div')(({ theme }) => ({
 | 
				
			||||||
    display: 'flex',
 | 
					    display: 'flex',
 | 
				
			||||||
    width: '100%',
 | 
					    width: '100%',
 | 
				
			||||||
    [theme.breakpoints.down(1000)]: {
 | 
					    gap: theme.spacing(2),
 | 
				
			||||||
 | 
					    [theme.breakpoints.down('md')]: {
 | 
				
			||||||
        flexDirection: 'column',
 | 
					        flexDirection: 'column',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
@ -30,57 +28,43 @@ const StyledContainer = styled('div')(({ theme }) => ({
 | 
				
			|||||||
const StyledMainContent = styled('div')(({ theme }) => ({
 | 
					const StyledMainContent = styled('div')(({ theme }) => ({
 | 
				
			||||||
    display: 'flex',
 | 
					    display: 'flex',
 | 
				
			||||||
    flexDirection: 'column',
 | 
					    flexDirection: 'column',
 | 
				
			||||||
    width: `calc(100% - (350px + 1rem))`,
 | 
					    flexGrow: 1,
 | 
				
			||||||
    [theme.breakpoints.down(1000)]: {
 | 
					    gap: theme.spacing(2),
 | 
				
			||||||
        width: '100%',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const FeatureOverview = () => {
 | 
					export const FeatureOverview = () => {
 | 
				
			||||||
    const navigate = useNavigate();
 | 
					    const navigate = useNavigate();
 | 
				
			||||||
    const projectId = useRequiredPathParam('projectId');
 | 
					    const projectId = useRequiredPathParam('projectId');
 | 
				
			||||||
    const featureId = useRequiredPathParam('featureId');
 | 
					    const featureId = useRequiredPathParam('featureId');
 | 
				
			||||||
    const featurePath = formatFeaturePath(projectId, featureId);
 | 
					    const featurePath = formatFeaturePath(projectId, featureId);
 | 
				
			||||||
    const { hiddenEnvironments, setHiddenEnvironments } =
 | 
					    const { hiddenEnvironments, onEnvironmentVisibilityChange } =
 | 
				
			||||||
        useHiddenEnvironments();
 | 
					        useEnvironmentVisibility();
 | 
				
			||||||
    const onSidebarClose = () => navigate(featurePath);
 | 
					    const onSidebarClose = () => navigate(featurePath);
 | 
				
			||||||
    usePageTitle(featureId);
 | 
					    usePageTitle(featureId);
 | 
				
			||||||
    const { setLastViewed } = useLastViewedFlags();
 | 
					    const { setLastViewed } = useLastViewedFlags();
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        setLastViewed({ featureId, projectId });
 | 
					        setLastViewed({ featureId, projectId });
 | 
				
			||||||
    }, [featureId]);
 | 
					    }, [featureId]);
 | 
				
			||||||
    const [environmentId, setEnvironmentId] = useState('');
 | 
					 | 
				
			||||||
    const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
 | 
					    const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!flagOverviewRedesign) {
 | 
				
			||||||
 | 
					        return <LegacyFleatureOverview />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <StyledContainer>
 | 
					        <StyledContainer>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                {flagOverviewRedesign ? (
 | 
					                <FeatureOverviewMetaData
 | 
				
			||||||
                    <>
 | 
					 | 
				
			||||||
                        <NewFeatureOverviewMetaData />
 | 
					 | 
				
			||||||
                        <NewFeatureOverviewSidePanel
 | 
					 | 
				
			||||||
                            environmentId={environmentId}
 | 
					 | 
				
			||||||
                            setEnvironmentId={setEnvironmentId}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                    </>
 | 
					 | 
				
			||||||
                ) : (
 | 
					 | 
				
			||||||
                    <>
 | 
					 | 
				
			||||||
                        <OldFeatureOverviewMetaData />
 | 
					 | 
				
			||||||
                        <OldFeatureOverviewSidePanel
 | 
					 | 
				
			||||||
                    hiddenEnvironments={hiddenEnvironments}
 | 
					                    hiddenEnvironments={hiddenEnvironments}
 | 
				
			||||||
                            setHiddenEnvironments={setHiddenEnvironments}
 | 
					                    onEnvironmentVisibilityChange={
 | 
				
			||||||
 | 
					                        onEnvironmentVisibilityChange
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                    </>
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <StyledMainContent>
 | 
					            <StyledMainContent>
 | 
				
			||||||
                {flagOverviewRedesign ? (
 | 
					                <FeatureOverviewEnvironment
 | 
				
			||||||
                    <NewFeatureOverviewEnvironment
 | 
					                    hiddenEnvironments={hiddenEnvironments}
 | 
				
			||||||
                        environmentId={environmentId}
 | 
					 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                ) : (
 | 
					 | 
				
			||||||
                    <FeatureOverviewEnvironments />
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
            </StyledMainContent>
 | 
					            </StyledMainContent>
 | 
				
			||||||
            <Routes>
 | 
					            <Routes>
 | 
				
			||||||
                <Route
 | 
					                <Route
 | 
				
			||||||
@ -111,5 +95,3 @@ const FeatureOverview = () => {
 | 
				
			|||||||
        </StyledContainer>
 | 
					        </StyledContainer>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default FeatureOverview;
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -29,6 +29,7 @@ export const AddTagButton: FC<AddTagButtonProps> = ({ project, onClick }) => (
 | 
				
			|||||||
        variant='text'
 | 
					        variant='text'
 | 
				
			||||||
        onClick={onClick}
 | 
					        onClick={onClick}
 | 
				
			||||||
        startIcon={<StyledAddIcon />}
 | 
					        startIcon={<StyledAddIcon />}
 | 
				
			||||||
 | 
					        data-loading
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
        Add tag
 | 
					        Add tag
 | 
				
			||||||
    </StyledAddTagButton>
 | 
					    </StyledAddTagButton>
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					import { Button, Checkbox, Menu, MenuItem, styled } from '@mui/material';
 | 
				
			||||||
 | 
					import { useState, type FC } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
 | 
				
			||||||
 | 
					import ExpandLessIcon from '@mui/icons-material/ExpandLess';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type EnvironmentVisibilityMenuProps = {
 | 
				
			||||||
 | 
					    environments: Array<{ name: string }>;
 | 
				
			||||||
 | 
					    hiddenEnvironments: string[];
 | 
				
			||||||
 | 
					    onChange: (name: string) => void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const buttonId = 'environment-visibility-button';
 | 
				
			||||||
 | 
					const menuId = 'environment-visibility-menu';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const StyledContainer = styled('div')(({ theme }) => ({
 | 
				
			||||||
 | 
					    display: 'flex',
 | 
				
			||||||
 | 
					    justifyContent: 'center',
 | 
				
			||||||
 | 
					    paddingTop: theme.spacing(4),
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const EnvironmentVisibilityMenu: FC<EnvironmentVisibilityMenuProps> = ({
 | 
				
			||||||
 | 
					    environments,
 | 
				
			||||||
 | 
					    hiddenEnvironments,
 | 
				
			||||||
 | 
					    onChange,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					    const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
 | 
				
			||||||
 | 
					    const isOpen = Boolean(anchorEl);
 | 
				
			||||||
 | 
					    const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
 | 
				
			||||||
 | 
					        setAnchorEl(event.currentTarget);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const handleClose = () => {
 | 
				
			||||||
 | 
					        setAnchorEl(null);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <StyledContainer>
 | 
				
			||||||
 | 
					            <Button
 | 
				
			||||||
 | 
					                onClick={handleOpen}
 | 
				
			||||||
 | 
					                endIcon={isOpen ? <ExpandLessIcon /> : <ExpandMoreIcon />}
 | 
				
			||||||
 | 
					                variant='outlined'
 | 
				
			||||||
 | 
					                id={buttonId}
 | 
				
			||||||
 | 
					                aria-controls={isOpen ? menuId : undefined}
 | 
				
			||||||
 | 
					                aria-haspopup='true'
 | 
				
			||||||
 | 
					                aria-expanded={isOpen ? 'true' : undefined}
 | 
				
			||||||
 | 
					                data-loading
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                Hide/show environments
 | 
				
			||||||
 | 
					            </Button>
 | 
				
			||||||
 | 
					            <Menu
 | 
				
			||||||
 | 
					                id={menuId}
 | 
				
			||||||
 | 
					                anchorEl={anchorEl}
 | 
				
			||||||
 | 
					                open={isOpen}
 | 
				
			||||||
 | 
					                onClose={handleClose}
 | 
				
			||||||
 | 
					                MenuListProps={{ 'aria-labelledby': buttonId }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                {environments.map(({ name }) => (
 | 
				
			||||||
 | 
					                    <MenuItem key={name} onClick={() => onChange(name)}>
 | 
				
			||||||
 | 
					                        <Checkbox
 | 
				
			||||||
 | 
					                            onChange={() => onChange(name)}
 | 
				
			||||||
 | 
					                            checked={!hiddenEnvironments?.includes(name)}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        {name}
 | 
				
			||||||
 | 
					                    </MenuItem>
 | 
				
			||||||
 | 
					                ))}
 | 
				
			||||||
 | 
					            </Menu>
 | 
				
			||||||
 | 
					        </StyledContainer>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					import { useLocalStorageState } from 'hooks/useLocalStorageState';
 | 
				
			||||||
 | 
					import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
 | 
				
			||||||
 | 
					import { createLocalStorage } from 'utils/createLocalStorage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reading legacy value will be safely refactored out in a next version - related to `flagOverviewRedesign` flag
 | 
				
			||||||
 | 
					const { value: legacyStoreValue } = createLocalStorage<{
 | 
				
			||||||
 | 
					    hiddenEnvironments?: Array<string>;
 | 
				
			||||||
 | 
					}>('global:v1', {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useEnvironmentVisibility = () => {
 | 
				
			||||||
 | 
					    const [value, setValue] = useLocalStorageState<Array<string>>(
 | 
				
			||||||
 | 
					        'environment-visibiilty',
 | 
				
			||||||
 | 
					        legacyStoreValue?.hiddenEnvironments || [],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const { trackEvent } = usePlausibleTracker();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onEnvironmentVisibilityChange = (environment: string) => {
 | 
				
			||||||
 | 
					        if (value.includes(environment)) {
 | 
				
			||||||
 | 
					            setValue(value.filter((env) => env !== environment));
 | 
				
			||||||
 | 
					            trackEvent('hidden_environment', {
 | 
				
			||||||
 | 
					                props: {
 | 
				
			||||||
 | 
					                    eventType: `environment unhidden`,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            setValue([...value, environment]);
 | 
				
			||||||
 | 
					            trackEvent('hidden_environment', {
 | 
				
			||||||
 | 
					                props: {
 | 
				
			||||||
 | 
					                    eventType: `environment hidden`,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        hiddenEnvironments: value,
 | 
				
			||||||
 | 
					        onEnvironmentVisibilityChange,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
 | 
					import { type FC, useState } from 'react';
 | 
				
			||||||
import { styled } from '@mui/material';
 | 
					import { styled } from '@mui/material';
 | 
				
			||||||
import { useNavigate } from 'react-router-dom';
 | 
					import { useNavigate } from 'react-router-dom';
 | 
				
			||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
					import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
				
			||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
					import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
				
			||||||
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
 | 
					import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
 | 
				
			||||||
import { useState } from 'react';
 | 
					 | 
				
			||||||
import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
 | 
					import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
 | 
				
			||||||
import { formatDateYMD } from 'utils/formatDate';
 | 
					import { formatDateYMD } from 'utils/formatDate';
 | 
				
			||||||
import { parseISO } from 'date-fns';
 | 
					import { parseISO } from 'date-fns';
 | 
				
			||||||
@ -15,6 +15,7 @@ import { MarkCompletedDialogue } from '../FeatureLifecycle/MarkCompletedDialogue
 | 
				
			|||||||
import { TagRow } from './TagRow';
 | 
					import { TagRow } from './TagRow';
 | 
				
			||||||
import { capitalizeFirst } from 'utils/capitalizeFirst';
 | 
					import { capitalizeFirst } from 'utils/capitalizeFirst';
 | 
				
			||||||
import { Collaborators } from './Collaborators';
 | 
					import { Collaborators } from './Collaborators';
 | 
				
			||||||
 | 
					import { EnvironmentVisibilityMenu } from './EnvironmentVisibilityMenu/EnvironmentVisibilityMenu';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const StyledMetaDataContainer = styled('div')(({ theme }) => ({
 | 
					const StyledMetaDataContainer = styled('div')(({ theme }) => ({
 | 
				
			||||||
    padding: theme.spacing(3),
 | 
					    padding: theme.spacing(3),
 | 
				
			||||||
@ -24,7 +25,8 @@ const StyledMetaDataContainer = styled('div')(({ theme }) => ({
 | 
				
			|||||||
    flexDirection: 'column',
 | 
					    flexDirection: 'column',
 | 
				
			||||||
    gap: theme.spacing(2),
 | 
					    gap: theme.spacing(2),
 | 
				
			||||||
    width: '350px',
 | 
					    width: '350px',
 | 
				
			||||||
    [theme.breakpoints.down(1000)]: {
 | 
					    border: `1px solid ${theme.palette.divider}`,
 | 
				
			||||||
 | 
					    [theme.breakpoints.down('md')]: {
 | 
				
			||||||
        width: '100%',
 | 
					        width: '100%',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
@ -63,7 +65,15 @@ export const StyledMetaDataItemValue = styled('div')(({ theme }) => ({
 | 
				
			|||||||
    gap: theme.spacing(1),
 | 
					    gap: theme.spacing(1),
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const FeatureOverviewMetaData = () => {
 | 
					type FeatureOverviewMetaDataProps = {
 | 
				
			||||||
 | 
					    hiddenEnvironments?: string[];
 | 
				
			||||||
 | 
					    onEnvironmentVisibilityChange?: (environment: string) => void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FeatureOverviewMetaData: FC<FeatureOverviewMetaDataProps> = ({
 | 
				
			||||||
 | 
					    hiddenEnvironments,
 | 
				
			||||||
 | 
					    onEnvironmentVisibilityChange,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
    const projectId = useRequiredPathParam('projectId');
 | 
					    const projectId = useRequiredPathParam('projectId');
 | 
				
			||||||
    const featureId = useRequiredPathParam('featureId');
 | 
					    const featureId = useRequiredPathParam('featureId');
 | 
				
			||||||
    const { feature, refetchFeature } = useFeature(projectId, featureId);
 | 
					    const { feature, refetchFeature } = useFeature(projectId, featureId);
 | 
				
			||||||
@ -156,6 +166,13 @@ const FeatureOverviewMetaData = () => {
 | 
				
			|||||||
                        <DependencyRow feature={feature} />
 | 
					                        <DependencyRow feature={feature} />
 | 
				
			||||||
                    ) : null}
 | 
					                    ) : null}
 | 
				
			||||||
                    <TagRow feature={feature} />
 | 
					                    <TagRow feature={feature} />
 | 
				
			||||||
 | 
					                    {onEnvironmentVisibilityChange ? (
 | 
				
			||||||
 | 
					                        <EnvironmentVisibilityMenu
 | 
				
			||||||
 | 
					                            environments={feature.environments || []}
 | 
				
			||||||
 | 
					                            hiddenEnvironments={hiddenEnvironments || []}
 | 
				
			||||||
 | 
					                            onChange={onEnvironmentVisibilityChange}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    ) : null}
 | 
				
			||||||
                </StyledBody>
 | 
					                </StyledBody>
 | 
				
			||||||
            </StyledMetaDataContainer>
 | 
					            </StyledMetaDataContainer>
 | 
				
			||||||
            {feature.children.length > 0 ? (
 | 
					            {feature.children.length > 0 ? (
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ const StyledContainer = styled(Box)(({ theme }) => ({
 | 
				
			|||||||
    flexDirection: 'column',
 | 
					    flexDirection: 'column',
 | 
				
			||||||
    gap: theme.spacing(2),
 | 
					    gap: theme.spacing(2),
 | 
				
			||||||
    width: '350px',
 | 
					    width: '350px',
 | 
				
			||||||
    [theme.breakpoints.down(1000)]: {
 | 
					    [theme.breakpoints.down('md')]: {
 | 
				
			||||||
        width: '100%',
 | 
					        width: '100%',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
 | 
				
			||||||
 | 
					import { Route, Routes, useNavigate } from 'react-router-dom';
 | 
				
			||||||
 | 
					import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    FeatureStrategyEdit,
 | 
				
			||||||
 | 
					    formatFeaturePath,
 | 
				
			||||||
 | 
					} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
 | 
				
			||||||
 | 
					import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
				
			||||||
 | 
					import { usePageTitle } from 'hooks/usePageTitle';
 | 
				
			||||||
 | 
					import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments';
 | 
				
			||||||
 | 
					import { styled } from '@mui/material';
 | 
				
			||||||
 | 
					import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
 | 
				
			||||||
 | 
					import { useEffect } from 'react';
 | 
				
			||||||
 | 
					import { useLastViewedFlags } from 'hooks/useLastViewedFlags';
 | 
				
			||||||
 | 
					import OldFeatureOverviewMetaData from './FeatureOverviewMetaData/OldFeatureOverviewMetaData';
 | 
				
			||||||
 | 
					import { OldFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const StyledContainer = styled('div')(({ theme }) => ({
 | 
				
			||||||
 | 
					    display: 'flex',
 | 
				
			||||||
 | 
					    width: '100%',
 | 
				
			||||||
 | 
					    [theme.breakpoints.down(1000)]: {
 | 
				
			||||||
 | 
					        flexDirection: 'column',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const StyledMainContent = styled('div')(({ theme }) => ({
 | 
				
			||||||
 | 
					    display: 'flex',
 | 
				
			||||||
 | 
					    flexDirection: 'column',
 | 
				
			||||||
 | 
					    width: `calc(100% - (350px + 1rem))`,
 | 
				
			||||||
 | 
					    [theme.breakpoints.down(1000)]: {
 | 
				
			||||||
 | 
					        width: '100%',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FeatureOverview = () => {
 | 
				
			||||||
 | 
					    const navigate = useNavigate();
 | 
				
			||||||
 | 
					    const projectId = useRequiredPathParam('projectId');
 | 
				
			||||||
 | 
					    const featureId = useRequiredPathParam('featureId');
 | 
				
			||||||
 | 
					    const featurePath = formatFeaturePath(projectId, featureId);
 | 
				
			||||||
 | 
					    const { hiddenEnvironments, setHiddenEnvironments } =
 | 
				
			||||||
 | 
					        useHiddenEnvironments();
 | 
				
			||||||
 | 
					    const onSidebarClose = () => navigate(featurePath);
 | 
				
			||||||
 | 
					    usePageTitle(featureId);
 | 
				
			||||||
 | 
					    const { setLastViewed } = useLastViewedFlags();
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        setLastViewed({ featureId, projectId });
 | 
				
			||||||
 | 
					    }, [featureId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <StyledContainer>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <OldFeatureOverviewMetaData />
 | 
				
			||||||
 | 
					                <OldFeatureOverviewSidePanel
 | 
				
			||||||
 | 
					                    hiddenEnvironments={hiddenEnvironments}
 | 
				
			||||||
 | 
					                    setHiddenEnvironments={setHiddenEnvironments}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <StyledMainContent>
 | 
				
			||||||
 | 
					                <FeatureOverviewEnvironments />
 | 
				
			||||||
 | 
					            </StyledMainContent>
 | 
				
			||||||
 | 
					            <Routes>
 | 
				
			||||||
 | 
					                <Route
 | 
				
			||||||
 | 
					                    path='strategies/create'
 | 
				
			||||||
 | 
					                    element={
 | 
				
			||||||
 | 
					                        <SidebarModal
 | 
				
			||||||
 | 
					                            label='Create feature strategy'
 | 
				
			||||||
 | 
					                            onClose={onSidebarClose}
 | 
				
			||||||
 | 
					                            open
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <FeatureStrategyCreate />
 | 
				
			||||||
 | 
					                        </SidebarModal>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <Route
 | 
				
			||||||
 | 
					                    path='strategies/edit'
 | 
				
			||||||
 | 
					                    element={
 | 
				
			||||||
 | 
					                        <SidebarModal
 | 
				
			||||||
 | 
					                            label='Edit feature strategy'
 | 
				
			||||||
 | 
					                            onClose={onSidebarClose}
 | 
				
			||||||
 | 
					                            open
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <FeatureStrategyEdit />
 | 
				
			||||||
 | 
					                        </SidebarModal>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </Routes>
 | 
				
			||||||
 | 
					        </StyledContainer>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default FeatureOverview;
 | 
				
			||||||
@ -8,7 +8,6 @@ import { Alert, Pagination, styled } from '@mui/material';
 | 
				
			|||||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
 | 
					import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
 | 
				
			||||||
import { formatUnknownError } from 'utils/formatUnknownError';
 | 
					import { formatUnknownError } from 'utils/formatUnknownError';
 | 
				
			||||||
import useToast from 'hooks/useToast';
 | 
					import useToast from 'hooks/useToast';
 | 
				
			||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
					 | 
				
			||||||
import { StrategyDraggableItem } from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem';
 | 
					import { StrategyDraggableItem } from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem';
 | 
				
			||||||
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
 | 
					import type { IFeatureEnvironment } from 'interfaces/featureToggle';
 | 
				
			||||||
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
 | 
					import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
 | 
				
			||||||
@ -230,25 +229,14 @@ export const FeatureOverviewEnvironmentBody = ({
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <StyledAccordionBody>
 | 
					        <StyledAccordionBody>
 | 
				
			||||||
            <StyledAccordionBodyInnerContainer>
 | 
					            <StyledAccordionBodyInnerContainer>
 | 
				
			||||||
                <ConditionallyRender
 | 
					                {(releasePlans.length > 0 || strategiesToDisplay.length > 0) &&
 | 
				
			||||||
                    condition={
 | 
					                isDisabled ? (
 | 
				
			||||||
                        (releasePlans.length > 0 ||
 | 
					 | 
				
			||||||
                            strategiesToDisplay.length > 0) &&
 | 
					 | 
				
			||||||
                        isDisabled
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    show={() => (
 | 
					 | 
				
			||||||
                    <Alert severity='warning' sx={{ mb: 2 }}>
 | 
					                    <Alert severity='warning' sx={{ mb: 2 }}>
 | 
				
			||||||
                            This environment is disabled, which means that none
 | 
					                        This environment is disabled, which means that none of
 | 
				
			||||||
                            of your strategies are executing.
 | 
					                        your strategies are executing.
 | 
				
			||||||
                    </Alert>
 | 
					                    </Alert>
 | 
				
			||||||
                    )}
 | 
					                ) : null}
 | 
				
			||||||
                />
 | 
					                {releasePlans.length > 0 || strategiesToDisplay.length > 0 ? (
 | 
				
			||||||
                <ConditionallyRender
 | 
					 | 
				
			||||||
                    condition={
 | 
					 | 
				
			||||||
                        releasePlans.length > 0 ||
 | 
					 | 
				
			||||||
                        strategiesToDisplay.length > 0
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    show={
 | 
					 | 
				
			||||||
                    <>
 | 
					                    <>
 | 
				
			||||||
                        {releasePlans.map((plan) => (
 | 
					                        {releasePlans.map((plan) => (
 | 
				
			||||||
                            <ReleasePlan
 | 
					                            <ReleasePlan
 | 
				
			||||||
@ -257,26 +245,15 @@ export const FeatureOverviewEnvironmentBody = ({
 | 
				
			|||||||
                                environmentIsDisabled={isDisabled}
 | 
					                                environmentIsDisabled={isDisabled}
 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
                        ))}
 | 
					                        ))}
 | 
				
			||||||
                            <ConditionallyRender
 | 
					                        {releasePlans.length > 0 && strategies.length > 0 ? (
 | 
				
			||||||
                                condition={
 | 
					 | 
				
			||||||
                                    releasePlans.length > 0 &&
 | 
					 | 
				
			||||||
                                    strategies.length > 0
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                                show={
 | 
					 | 
				
			||||||
                            <SectionSeparator>
 | 
					                            <SectionSeparator>
 | 
				
			||||||
                                <StyledBadge>OR</StyledBadge>
 | 
					                                <StyledBadge>OR</StyledBadge>
 | 
				
			||||||
                            </SectionSeparator>
 | 
					                            </SectionSeparator>
 | 
				
			||||||
                                }
 | 
					                        ) : null}
 | 
				
			||||||
                            />
 | 
					                        {strategiesToDisplay.length < 50 ||
 | 
				
			||||||
                            <ConditionallyRender
 | 
					                        !manyStrategiesPagination ? (
 | 
				
			||||||
                                condition={
 | 
					 | 
				
			||||||
                                    strategiesToDisplay.length < 50 ||
 | 
					 | 
				
			||||||
                                    !manyStrategiesPagination
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                                show={
 | 
					 | 
				
			||||||
                            <>
 | 
					                            <>
 | 
				
			||||||
                                        {strategiesToDisplay.map(
 | 
					                                {strategiesToDisplay.map((strategy, index) => (
 | 
				
			||||||
                                            (strategy, index) => (
 | 
					 | 
				
			||||||
                                    <StrategyDraggableItem
 | 
					                                    <StrategyDraggableItem
 | 
				
			||||||
                                        key={strategy.id}
 | 
					                                        key={strategy.id}
 | 
				
			||||||
                                        strategy={strategy}
 | 
					                                        strategy={strategy}
 | 
				
			||||||
@ -284,52 +261,36 @@ export const FeatureOverviewEnvironmentBody = ({
 | 
				
			|||||||
                                        environmentName={
 | 
					                                        environmentName={
 | 
				
			||||||
                                            featureEnvironment.name
 | 
					                                            featureEnvironment.name
 | 
				
			||||||
                                        }
 | 
					                                        }
 | 
				
			||||||
                                                    otherEnvironments={
 | 
					                                        otherEnvironments={otherEnvironments}
 | 
				
			||||||
                                                        otherEnvironments
 | 
					 | 
				
			||||||
                                                    }
 | 
					 | 
				
			||||||
                                        isDragging={
 | 
					                                        isDragging={
 | 
				
			||||||
                                                        dragItem?.id ===
 | 
					                                            dragItem?.id === strategy.id
 | 
				
			||||||
                                                        strategy.id
 | 
					 | 
				
			||||||
                                        }
 | 
					                                        }
 | 
				
			||||||
                                                    onDragStartRef={
 | 
					                                        onDragStartRef={onDragStartRef}
 | 
				
			||||||
                                                        onDragStartRef
 | 
					                                        onDragOver={onDragOver(strategy.id)}
 | 
				
			||||||
                                                    }
 | 
					 | 
				
			||||||
                                                    onDragOver={onDragOver(
 | 
					 | 
				
			||||||
                                                        strategy.id,
 | 
					 | 
				
			||||||
                                                    )}
 | 
					 | 
				
			||||||
                                        onDragEnd={onDragEnd}
 | 
					                                        onDragEnd={onDragEnd}
 | 
				
			||||||
                                    />
 | 
					                                    />
 | 
				
			||||||
                                            ),
 | 
					                                ))}
 | 
				
			||||||
                                        )}
 | 
					 | 
				
			||||||
                            </>
 | 
					                            </>
 | 
				
			||||||
                                }
 | 
					                        ) : (
 | 
				
			||||||
                                elseShow={
 | 
					 | 
				
			||||||
                            <>
 | 
					                            <>
 | 
				
			||||||
                                <Alert severity='error'>
 | 
					                                <Alert severity='error'>
 | 
				
			||||||
                                            We noticed you're using a high
 | 
					                                    We noticed you're using a high number of
 | 
				
			||||||
                                            number of activation strategies. To
 | 
					                                    activation strategies. To ensure a more
 | 
				
			||||||
                                            ensure a more targeted approach,
 | 
					                                    targeted approach, consider leveraging
 | 
				
			||||||
                                            consider leveraging constraints or
 | 
					                                    constraints or segments.
 | 
				
			||||||
                                            segments.
 | 
					 | 
				
			||||||
                                </Alert>
 | 
					                                </Alert>
 | 
				
			||||||
                                <br />
 | 
					                                <br />
 | 
				
			||||||
                                {page.map((strategy, index) => (
 | 
					                                {page.map((strategy, index) => (
 | 
				
			||||||
                                    <StrategyDraggableItem
 | 
					                                    <StrategyDraggableItem
 | 
				
			||||||
                                        key={strategy.id}
 | 
					                                        key={strategy.id}
 | 
				
			||||||
                                        strategy={strategy}
 | 
					                                        strategy={strategy}
 | 
				
			||||||
                                                index={
 | 
					                                        index={index + pageIndex * pageSize}
 | 
				
			||||||
                                                    index + pageIndex * pageSize
 | 
					 | 
				
			||||||
                                                }
 | 
					 | 
				
			||||||
                                        environmentName={
 | 
					                                        environmentName={
 | 
				
			||||||
                                            featureEnvironment.name
 | 
					                                            featureEnvironment.name
 | 
				
			||||||
                                        }
 | 
					                                        }
 | 
				
			||||||
                                                otherEnvironments={
 | 
					                                        otherEnvironments={otherEnvironments}
 | 
				
			||||||
                                                    otherEnvironments
 | 
					 | 
				
			||||||
                                                }
 | 
					 | 
				
			||||||
                                        isDragging={false}
 | 
					                                        isDragging={false}
 | 
				
			||||||
                                                onDragStartRef={
 | 
					                                        onDragStartRef={(() => {}) as any}
 | 
				
			||||||
                                                    (() => {}) as any
 | 
					 | 
				
			||||||
                                                }
 | 
					 | 
				
			||||||
                                        onDragOver={(() => {}) as any}
 | 
					                                        onDragOver={(() => {}) as any}
 | 
				
			||||||
                                        onDragEnd={(() => {}) as any}
 | 
					                                        onDragEnd={(() => {}) as any}
 | 
				
			||||||
                                    />
 | 
					                                    />
 | 
				
			||||||
@ -344,18 +305,15 @@ export const FeatureOverviewEnvironmentBody = ({
 | 
				
			|||||||
                                    }
 | 
					                                    }
 | 
				
			||||||
                                />
 | 
					                                />
 | 
				
			||||||
                            </>
 | 
					                            </>
 | 
				
			||||||
                                }
 | 
					                        )}
 | 
				
			||||||
                            />
 | 
					 | 
				
			||||||
                    </>
 | 
					                    </>
 | 
				
			||||||
                    }
 | 
					                ) : (
 | 
				
			||||||
                    elseShow={
 | 
					 | 
				
			||||||
                    <FeatureStrategyEmpty
 | 
					                    <FeatureStrategyEmpty
 | 
				
			||||||
                        projectId={projectId}
 | 
					                        projectId={projectId}
 | 
				
			||||||
                        featureId={featureId}
 | 
					                        featureId={featureId}
 | 
				
			||||||
                        environmentId={featureEnvironment.name}
 | 
					                        environmentId={featureEnvironment.name}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                    }
 | 
					                )}
 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
            </StyledAccordionBodyInnerContainer>
 | 
					            </StyledAccordionBodyInnerContainer>
 | 
				
			||||||
        </StyledAccordionBody>
 | 
					        </StyledAccordionBody>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@ import { Box, styled } from '@mui/material';
 | 
				
			|||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
					import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
				
			||||||
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
 | 
					import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
 | 
				
			||||||
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
 | 
					import { getFeatureMetrics } from 'utils/getFeatureMetrics';
 | 
				
			||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
					 | 
				
			||||||
import { FeatureOverviewEnvironmentBody } from './FeatureOverviewEnvironmentBody';
 | 
					import { FeatureOverviewEnvironmentBody } from './FeatureOverviewEnvironmentBody';
 | 
				
			||||||
import FeatureOverviewEnvironmentMetrics from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
 | 
					import FeatureOverviewEnvironmentMetrics from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
 | 
				
			||||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
 | 
					import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
 | 
				
			||||||
@ -13,6 +12,7 @@ const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
 | 
				
			|||||||
    padding: theme.spacing(1, 3),
 | 
					    padding: theme.spacing(1, 3),
 | 
				
			||||||
    borderRadius: theme.shape.borderRadiusLarge,
 | 
					    borderRadius: theme.shape.borderRadiusLarge,
 | 
				
			||||||
    backgroundColor: theme.palette.background.paper,
 | 
					    backgroundColor: theme.palette.background.paper,
 | 
				
			||||||
 | 
					    border: `1px solid ${theme.palette.divider}`,
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const StyledFeatureOverviewEnvironmentBody = styled(
 | 
					const StyledFeatureOverviewEnvironmentBody = styled(
 | 
				
			||||||
@ -52,18 +52,35 @@ const StyledHeaderTitle = styled('span')(({ theme }) => ({
 | 
				
			|||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface INewFeatureOverviewEnvironmentProps {
 | 
					interface INewFeatureOverviewEnvironmentProps {
 | 
				
			||||||
    environmentId: string;
 | 
					    hiddenEnvironments: string[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const NewFeatureOverviewEnvironment = ({
 | 
					export const FeatureOverviewEnvironment = ({
 | 
				
			||||||
    environmentId,
 | 
					    hiddenEnvironments,
 | 
				
			||||||
}: INewFeatureOverviewEnvironmentProps) => {
 | 
					}: INewFeatureOverviewEnvironmentProps) => {
 | 
				
			||||||
    const projectId = useRequiredPathParam('projectId');
 | 
					    const projectId = useRequiredPathParam('projectId');
 | 
				
			||||||
    const featureId = useRequiredPathParam('featureId');
 | 
					    const featureId = useRequiredPathParam('featureId');
 | 
				
			||||||
    const { metrics } = useFeatureMetrics(projectId, featureId);
 | 
					    const { metrics } = useFeatureMetrics(projectId, featureId);
 | 
				
			||||||
    const { feature } = useFeature(projectId, featureId);
 | 
					    const { feature } = useFeature(projectId, featureId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const featureMetrics = getFeatureMetrics(feature?.environments, metrics);
 | 
					    const environments =
 | 
				
			||||||
 | 
					        feature?.environments.filter(
 | 
				
			||||||
 | 
					            ({ name }) => !hiddenEnvironments.includes(name),
 | 
				
			||||||
 | 
					        ) || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!environments || environments.length === 0) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <StyledFeatureOverviewEnvironment className='skeleton'>
 | 
				
			||||||
 | 
					                <Box sx={{ height: '400px' }} />
 | 
				
			||||||
 | 
					            </StyledFeatureOverviewEnvironment>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return environments.map(({ name: environmentId }) => {
 | 
				
			||||||
 | 
					        const featureMetrics = getFeatureMetrics(
 | 
				
			||||||
 | 
					            feature?.environments,
 | 
				
			||||||
 | 
					            metrics,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        const environmentMetric = featureMetrics.find(
 | 
					        const environmentMetric = featureMetrics.find(
 | 
				
			||||||
            ({ environment }) => environment === environmentId,
 | 
					            ({ environment }) => environment === environmentId,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -71,15 +88,12 @@ export const NewFeatureOverviewEnvironment = ({
 | 
				
			|||||||
            ({ name }) => name === environmentId,
 | 
					            ({ name }) => name === environmentId,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!featureEnvironment)
 | 
					        if (!featureEnvironment) {
 | 
				
			||||||
        return (
 | 
					            return null;
 | 
				
			||||||
            <StyledFeatureOverviewEnvironment className='skeleton'>
 | 
					        }
 | 
				
			||||||
                <Box sx={{ height: '400px' }} />
 | 
					 | 
				
			||||||
            </StyledFeatureOverviewEnvironment>
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
        <StyledFeatureOverviewEnvironment>
 | 
					            <StyledFeatureOverviewEnvironment key={environmentId}>
 | 
				
			||||||
                <StyledHeader data-loading>
 | 
					                <StyledHeader data-loading>
 | 
				
			||||||
                    <StyledHeaderToggleContainer>
 | 
					                    <StyledHeaderToggleContainer>
 | 
				
			||||||
                        <FeatureOverviewEnvironmentToggle
 | 
					                        <FeatureOverviewEnvironmentToggle
 | 
				
			||||||
@ -89,7 +103,9 @@ export const NewFeatureOverviewEnvironment = ({
 | 
				
			|||||||
                            <StyledHeaderTitleLabel>
 | 
					                            <StyledHeaderTitleLabel>
 | 
				
			||||||
                                Environment
 | 
					                                Environment
 | 
				
			||||||
                            </StyledHeaderTitleLabel>
 | 
					                            </StyledHeaderTitleLabel>
 | 
				
			||||||
                        <StyledHeaderTitle>{environmentId}</StyledHeaderTitle>
 | 
					                            <StyledHeaderTitle>
 | 
				
			||||||
 | 
					                                {environmentId}
 | 
				
			||||||
 | 
					                            </StyledHeaderTitle>
 | 
				
			||||||
                        </StyledHeaderTitleContainer>
 | 
					                        </StyledHeaderTitleContainer>
 | 
				
			||||||
                    </StyledHeaderToggleContainer>
 | 
					                    </StyledHeaderToggleContainer>
 | 
				
			||||||
                    <FeatureOverviewEnvironmentMetrics
 | 
					                    <FeatureOverviewEnvironmentMetrics
 | 
				
			||||||
@ -97,7 +113,6 @@ export const NewFeatureOverviewEnvironment = ({
 | 
				
			|||||||
                        disabled={!featureEnvironment.enabled}
 | 
					                        disabled={!featureEnvironment.enabled}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </StyledHeader>
 | 
					                </StyledHeader>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <StyledFeatureOverviewEnvironmentBody
 | 
					                <StyledFeatureOverviewEnvironmentBody
 | 
				
			||||||
                    featureEnvironment={featureEnvironment}
 | 
					                    featureEnvironment={featureEnvironment}
 | 
				
			||||||
                    isDisabled={!featureEnvironment.enabled}
 | 
					                    isDisabled={!featureEnvironment.enabled}
 | 
				
			||||||
@ -105,9 +120,7 @@ export const NewFeatureOverviewEnvironment = ({
 | 
				
			|||||||
                        .map(({ name }) => name)
 | 
					                        .map(({ name }) => name)
 | 
				
			||||||
                        .filter((name) => name !== environmentId)}
 | 
					                        .filter((name) => name !== environmentId)}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            <ConditionallyRender
 | 
					                {featureEnvironment?.strategies?.length > 0 ? (
 | 
				
			||||||
                condition={(featureEnvironment?.strategies?.length || 0) > 0}
 | 
					 | 
				
			||||||
                show={
 | 
					 | 
				
			||||||
                    <>
 | 
					                    <>
 | 
				
			||||||
                        <Box
 | 
					                        <Box
 | 
				
			||||||
                            sx={{
 | 
					                            sx={{
 | 
				
			||||||
@ -124,8 +137,8 @@ export const NewFeatureOverviewEnvironment = ({
 | 
				
			|||||||
                            />
 | 
					                            />
 | 
				
			||||||
                        </Box>
 | 
					                        </Box>
 | 
				
			||||||
                    </>
 | 
					                    </>
 | 
				
			||||||
                }
 | 
					                ) : null}
 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            </StyledFeatureOverviewEnvironment>
 | 
					            </StyledFeatureOverviewEnvironment>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Link, Route, Routes } from 'react-router-dom';
 | 
					import { Link, Route, Routes } from 'react-router-dom';
 | 
				
			||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
					import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
				
			||||||
import FeatureLog from './FeatureLog/FeatureLog';
 | 
					import FeatureLog from './FeatureLog/FeatureLog';
 | 
				
			||||||
import FeatureOverview from './FeatureOverview/FeatureOverview';
 | 
					import { FeatureOverview } from './FeatureOverview/FeatureOverview';
 | 
				
			||||||
import { FeatureEnvironmentVariants } from './FeatureVariants/FeatureEnvironmentVariants/FeatureEnvironmentVariants';
 | 
					import { FeatureEnvironmentVariants } from './FeatureVariants/FeatureEnvironmentVariants/FeatureEnvironmentVariants';
 | 
				
			||||||
import { FeatureMetrics } from './FeatureMetrics/FeatureMetrics';
 | 
					import { FeatureMetrics } from './FeatureMetrics/FeatureMetrics';
 | 
				
			||||||
import { FeatureSettings } from './FeatureSettings/FeatureSettings';
 | 
					import { FeatureSettings } from './FeatureSettings/FeatureSettings';
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,9 @@ interface IGlobalStore {
 | 
				
			|||||||
    hiddenEnvironments?: Array<string>;
 | 
					    hiddenEnvironments?: Array<string>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @deprecated use tested `useLocalStorageState` hook instead
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
export const useGlobalLocalStorage = () => {
 | 
					export const useGlobalLocalStorage = () => {
 | 
				
			||||||
    const { value, setValue } = createLocalStorage<IGlobalStore>(
 | 
					    const { value, setValue } = createLocalStorage<IGlobalStore>(
 | 
				
			||||||
        'global:v1',
 | 
					        'global:v1',
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,9 @@ import { useGlobalLocalStorage } from './useGlobalLocalStorage';
 | 
				
			|||||||
import { useState } from 'react';
 | 
					import { useState } from 'react';
 | 
				
			||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
 | 
					import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @deprecated remove with `flagOverviewRedesign`
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
export const useHiddenEnvironments = () => {
 | 
					export const useHiddenEnvironments = () => {
 | 
				
			||||||
    const { trackEvent } = usePlausibleTracker();
 | 
					    const { trackEvent } = usePlausibleTracker();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -25,11 +28,6 @@ export const useHiddenEnvironments = () => {
 | 
				
			|||||||
                });
 | 
					                });
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                hiddenEnvironments.add(environment);
 | 
					                hiddenEnvironments.add(environment);
 | 
				
			||||||
                trackEvent('hidden_environment', {
 | 
					 | 
				
			||||||
                    props: {
 | 
					 | 
				
			||||||
                        eventType: `environment hidden`,
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            setStoredHiddenEnvironments(hiddenEnvironments);
 | 
					            setStoredHiddenEnvironments(hiddenEnvironments);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user