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 FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
 | 
			
		||||
import FeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMetaData';
 | 
			
		||||
import { Route, Routes, useNavigate } from 'react-router-dom';
 | 
			
		||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
 | 
			
		||||
import {
 | 
			
		||||
@ -8,21 +7,20 @@ import {
 | 
			
		||||
} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
 | 
			
		||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
			
		||||
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 { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
 | 
			
		||||
import { useEffect, useState } from 'react';
 | 
			
		||||
import { useEffect } from 'react';
 | 
			
		||||
import { useLastViewedFlags } from 'hooks/useLastViewedFlags';
 | 
			
		||||
import { useUiFlag } from 'hooks/useUiFlag';
 | 
			
		||||
import OldFeatureOverviewMetaData from './FeatureOverviewMetaData/OldFeatureOverviewMetaData';
 | 
			
		||||
import { OldFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel';
 | 
			
		||||
import { NewFeatureOverviewEnvironment } from './NewFeatureOverviewEnvironment/NewFeatureOverviewEnvironment';
 | 
			
		||||
import { FeatureOverviewEnvironment } from './NewFeatureOverviewEnvironment/NewFeatureOverviewEnvironment';
 | 
			
		||||
import { default as LegacyFleatureOverview } from './LegacyFeatureOverview';
 | 
			
		||||
import { useEnvironmentVisibility } from './FeatureOverviewMetaData/EnvironmentVisibilityMenu/hooks/useEnvironmentVisibility';
 | 
			
		||||
 | 
			
		||||
const StyledContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    width: '100%',
 | 
			
		||||
    [theme.breakpoints.down(1000)]: {
 | 
			
		||||
    gap: theme.spacing(2),
 | 
			
		||||
    [theme.breakpoints.down('md')]: {
 | 
			
		||||
        flexDirection: 'column',
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
@ -30,57 +28,43 @@ const StyledContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
const StyledMainContent = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    width: `calc(100% - (350px + 1rem))`,
 | 
			
		||||
    [theme.breakpoints.down(1000)]: {
 | 
			
		||||
        width: '100%',
 | 
			
		||||
    },
 | 
			
		||||
    flexGrow: 1,
 | 
			
		||||
    gap: theme.spacing(2),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const FeatureOverview = () => {
 | 
			
		||||
export const FeatureOverview = () => {
 | 
			
		||||
    const navigate = useNavigate();
 | 
			
		||||
    const projectId = useRequiredPathParam('projectId');
 | 
			
		||||
    const featureId = useRequiredPathParam('featureId');
 | 
			
		||||
    const featurePath = formatFeaturePath(projectId, featureId);
 | 
			
		||||
    const { hiddenEnvironments, setHiddenEnvironments } =
 | 
			
		||||
        useHiddenEnvironments();
 | 
			
		||||
    const { hiddenEnvironments, onEnvironmentVisibilityChange } =
 | 
			
		||||
        useEnvironmentVisibility();
 | 
			
		||||
    const onSidebarClose = () => navigate(featurePath);
 | 
			
		||||
    usePageTitle(featureId);
 | 
			
		||||
    const { setLastViewed } = useLastViewedFlags();
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setLastViewed({ featureId, projectId });
 | 
			
		||||
    }, [featureId]);
 | 
			
		||||
    const [environmentId, setEnvironmentId] = useState('');
 | 
			
		||||
    const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
 | 
			
		||||
 | 
			
		||||
    if (!flagOverviewRedesign) {
 | 
			
		||||
        return <LegacyFleatureOverview />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledContainer>
 | 
			
		||||
            <div>
 | 
			
		||||
                {flagOverviewRedesign ? (
 | 
			
		||||
                    <>
 | 
			
		||||
                        <NewFeatureOverviewMetaData />
 | 
			
		||||
                        <NewFeatureOverviewSidePanel
 | 
			
		||||
                            environmentId={environmentId}
 | 
			
		||||
                            setEnvironmentId={setEnvironmentId}
 | 
			
		||||
                        />
 | 
			
		||||
                    </>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <>
 | 
			
		||||
                        <OldFeatureOverviewMetaData />
 | 
			
		||||
                        <OldFeatureOverviewSidePanel
 | 
			
		||||
                <FeatureOverviewMetaData
 | 
			
		||||
                    hiddenEnvironments={hiddenEnvironments}
 | 
			
		||||
                            setHiddenEnvironments={setHiddenEnvironments}
 | 
			
		||||
                    onEnvironmentVisibilityChange={
 | 
			
		||||
                        onEnvironmentVisibilityChange
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
                    </>
 | 
			
		||||
                )}
 | 
			
		||||
            </div>
 | 
			
		||||
            <StyledMainContent>
 | 
			
		||||
                {flagOverviewRedesign ? (
 | 
			
		||||
                    <NewFeatureOverviewEnvironment
 | 
			
		||||
                        environmentId={environmentId}
 | 
			
		||||
                <FeatureOverviewEnvironment
 | 
			
		||||
                    hiddenEnvironments={hiddenEnvironments}
 | 
			
		||||
                />
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <FeatureOverviewEnvironments />
 | 
			
		||||
                )}
 | 
			
		||||
            </StyledMainContent>
 | 
			
		||||
            <Routes>
 | 
			
		||||
                <Route
 | 
			
		||||
@ -111,5 +95,3 @@ const FeatureOverview = () => {
 | 
			
		||||
        </StyledContainer>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default FeatureOverview;
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ export const AddTagButton: FC<AddTagButtonProps> = ({ project, onClick }) => (
 | 
			
		||||
        variant='text'
 | 
			
		||||
        onClick={onClick}
 | 
			
		||||
        startIcon={<StyledAddIcon />}
 | 
			
		||||
        data-loading
 | 
			
		||||
    >
 | 
			
		||||
        Add tag
 | 
			
		||||
    </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 { useNavigate } from 'react-router-dom';
 | 
			
		||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
			
		||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
 | 
			
		||||
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
 | 
			
		||||
import { useState } from 'react';
 | 
			
		||||
import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
 | 
			
		||||
import { formatDateYMD } from 'utils/formatDate';
 | 
			
		||||
import { parseISO } from 'date-fns';
 | 
			
		||||
@ -15,6 +15,7 @@ import { MarkCompletedDialogue } from '../FeatureLifecycle/MarkCompletedDialogue
 | 
			
		||||
import { TagRow } from './TagRow';
 | 
			
		||||
import { capitalizeFirst } from 'utils/capitalizeFirst';
 | 
			
		||||
import { Collaborators } from './Collaborators';
 | 
			
		||||
import { EnvironmentVisibilityMenu } from './EnvironmentVisibilityMenu/EnvironmentVisibilityMenu';
 | 
			
		||||
 | 
			
		||||
const StyledMetaDataContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    padding: theme.spacing(3),
 | 
			
		||||
@ -24,7 +25,8 @@ const StyledMetaDataContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    gap: theme.spacing(2),
 | 
			
		||||
    width: '350px',
 | 
			
		||||
    [theme.breakpoints.down(1000)]: {
 | 
			
		||||
    border: `1px solid ${theme.palette.divider}`,
 | 
			
		||||
    [theme.breakpoints.down('md')]: {
 | 
			
		||||
        width: '100%',
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
@ -63,7 +65,15 @@ export const StyledMetaDataItemValue = styled('div')(({ theme }) => ({
 | 
			
		||||
    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 featureId = useRequiredPathParam('featureId');
 | 
			
		||||
    const { feature, refetchFeature } = useFeature(projectId, featureId);
 | 
			
		||||
@ -156,6 +166,13 @@ const FeatureOverviewMetaData = () => {
 | 
			
		||||
                        <DependencyRow feature={feature} />
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                    <TagRow feature={feature} />
 | 
			
		||||
                    {onEnvironmentVisibilityChange ? (
 | 
			
		||||
                        <EnvironmentVisibilityMenu
 | 
			
		||||
                            environments={feature.environments || []}
 | 
			
		||||
                            hiddenEnvironments={hiddenEnvironments || []}
 | 
			
		||||
                            onChange={onEnvironmentVisibilityChange}
 | 
			
		||||
                        />
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                </StyledBody>
 | 
			
		||||
            </StyledMetaDataContainer>
 | 
			
		||||
            {feature.children.length > 0 ? (
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ const StyledContainer = styled(Box)(({ theme }) => ({
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    gap: theme.spacing(2),
 | 
			
		||||
    width: '350px',
 | 
			
		||||
    [theme.breakpoints.down(1000)]: {
 | 
			
		||||
    [theme.breakpoints.down('md')]: {
 | 
			
		||||
        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 { formatUnknownError } from 'utils/formatUnknownError';
 | 
			
		||||
import useToast from 'hooks/useToast';
 | 
			
		||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
			
		||||
import { StrategyDraggableItem } from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem';
 | 
			
		||||
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
 | 
			
		||||
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
 | 
			
		||||
@ -230,25 +229,14 @@ export const FeatureOverviewEnvironmentBody = ({
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledAccordionBody>
 | 
			
		||||
            <StyledAccordionBodyInnerContainer>
 | 
			
		||||
                <ConditionallyRender
 | 
			
		||||
                    condition={
 | 
			
		||||
                        (releasePlans.length > 0 ||
 | 
			
		||||
                            strategiesToDisplay.length > 0) &&
 | 
			
		||||
                        isDisabled
 | 
			
		||||
                    }
 | 
			
		||||
                    show={() => (
 | 
			
		||||
                {(releasePlans.length > 0 || strategiesToDisplay.length > 0) &&
 | 
			
		||||
                isDisabled ? (
 | 
			
		||||
                    <Alert severity='warning' sx={{ mb: 2 }}>
 | 
			
		||||
                            This environment is disabled, which means that none
 | 
			
		||||
                            of your strategies are executing.
 | 
			
		||||
                        This environment is disabled, which means that none of
 | 
			
		||||
                        your strategies are executing.
 | 
			
		||||
                    </Alert>
 | 
			
		||||
                    )}
 | 
			
		||||
                />
 | 
			
		||||
                <ConditionallyRender
 | 
			
		||||
                    condition={
 | 
			
		||||
                        releasePlans.length > 0 ||
 | 
			
		||||
                        strategiesToDisplay.length > 0
 | 
			
		||||
                    }
 | 
			
		||||
                    show={
 | 
			
		||||
                ) : null}
 | 
			
		||||
                {releasePlans.length > 0 || strategiesToDisplay.length > 0 ? (
 | 
			
		||||
                    <>
 | 
			
		||||
                        {releasePlans.map((plan) => (
 | 
			
		||||
                            <ReleasePlan
 | 
			
		||||
@ -257,26 +245,15 @@ export const FeatureOverviewEnvironmentBody = ({
 | 
			
		||||
                                environmentIsDisabled={isDisabled}
 | 
			
		||||
                            />
 | 
			
		||||
                        ))}
 | 
			
		||||
                            <ConditionallyRender
 | 
			
		||||
                                condition={
 | 
			
		||||
                                    releasePlans.length > 0 &&
 | 
			
		||||
                                    strategies.length > 0
 | 
			
		||||
                                }
 | 
			
		||||
                                show={
 | 
			
		||||
                        {releasePlans.length > 0 && strategies.length > 0 ? (
 | 
			
		||||
                            <SectionSeparator>
 | 
			
		||||
                                <StyledBadge>OR</StyledBadge>
 | 
			
		||||
                            </SectionSeparator>
 | 
			
		||||
                                }
 | 
			
		||||
                            />
 | 
			
		||||
                            <ConditionallyRender
 | 
			
		||||
                                condition={
 | 
			
		||||
                                    strategiesToDisplay.length < 50 ||
 | 
			
		||||
                                    !manyStrategiesPagination
 | 
			
		||||
                                }
 | 
			
		||||
                                show={
 | 
			
		||||
                        ) : null}
 | 
			
		||||
                        {strategiesToDisplay.length < 50 ||
 | 
			
		||||
                        !manyStrategiesPagination ? (
 | 
			
		||||
                            <>
 | 
			
		||||
                                        {strategiesToDisplay.map(
 | 
			
		||||
                                            (strategy, index) => (
 | 
			
		||||
                                {strategiesToDisplay.map((strategy, index) => (
 | 
			
		||||
                                    <StrategyDraggableItem
 | 
			
		||||
                                        key={strategy.id}
 | 
			
		||||
                                        strategy={strategy}
 | 
			
		||||
@ -284,52 +261,36 @@ export const FeatureOverviewEnvironmentBody = ({
 | 
			
		||||
                                        environmentName={
 | 
			
		||||
                                            featureEnvironment.name
 | 
			
		||||
                                        }
 | 
			
		||||
                                                    otherEnvironments={
 | 
			
		||||
                                                        otherEnvironments
 | 
			
		||||
                                                    }
 | 
			
		||||
                                        otherEnvironments={otherEnvironments}
 | 
			
		||||
                                        isDragging={
 | 
			
		||||
                                                        dragItem?.id ===
 | 
			
		||||
                                                        strategy.id
 | 
			
		||||
                                            dragItem?.id === strategy.id
 | 
			
		||||
                                        }
 | 
			
		||||
                                                    onDragStartRef={
 | 
			
		||||
                                                        onDragStartRef
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                    onDragOver={onDragOver(
 | 
			
		||||
                                                        strategy.id,
 | 
			
		||||
                                                    )}
 | 
			
		||||
                                        onDragStartRef={onDragStartRef}
 | 
			
		||||
                                        onDragOver={onDragOver(strategy.id)}
 | 
			
		||||
                                        onDragEnd={onDragEnd}
 | 
			
		||||
                                    />
 | 
			
		||||
                                            ),
 | 
			
		||||
                                        )}
 | 
			
		||||
                                ))}
 | 
			
		||||
                            </>
 | 
			
		||||
                                }
 | 
			
		||||
                                elseShow={
 | 
			
		||||
                        ) : (
 | 
			
		||||
                            <>
 | 
			
		||||
                                <Alert severity='error'>
 | 
			
		||||
                                            We noticed you're using a high
 | 
			
		||||
                                            number of activation strategies. To
 | 
			
		||||
                                            ensure a more targeted approach,
 | 
			
		||||
                                            consider leveraging constraints or
 | 
			
		||||
                                            segments.
 | 
			
		||||
                                    We noticed you're using a high number of
 | 
			
		||||
                                    activation strategies. To ensure a more
 | 
			
		||||
                                    targeted approach, consider leveraging
 | 
			
		||||
                                    constraints or segments.
 | 
			
		||||
                                </Alert>
 | 
			
		||||
                                <br />
 | 
			
		||||
                                {page.map((strategy, index) => (
 | 
			
		||||
                                    <StrategyDraggableItem
 | 
			
		||||
                                        key={strategy.id}
 | 
			
		||||
                                        strategy={strategy}
 | 
			
		||||
                                                index={
 | 
			
		||||
                                                    index + pageIndex * pageSize
 | 
			
		||||
                                                }
 | 
			
		||||
                                        index={index + pageIndex * pageSize}
 | 
			
		||||
                                        environmentName={
 | 
			
		||||
                                            featureEnvironment.name
 | 
			
		||||
                                        }
 | 
			
		||||
                                                otherEnvironments={
 | 
			
		||||
                                                    otherEnvironments
 | 
			
		||||
                                                }
 | 
			
		||||
                                        otherEnvironments={otherEnvironments}
 | 
			
		||||
                                        isDragging={false}
 | 
			
		||||
                                                onDragStartRef={
 | 
			
		||||
                                                    (() => {}) as any
 | 
			
		||||
                                                }
 | 
			
		||||
                                        onDragStartRef={(() => {}) as any}
 | 
			
		||||
                                        onDragOver={(() => {}) as any}
 | 
			
		||||
                                        onDragEnd={(() => {}) as any}
 | 
			
		||||
                                    />
 | 
			
		||||
@ -344,18 +305,15 @@ export const FeatureOverviewEnvironmentBody = ({
 | 
			
		||||
                                    }
 | 
			
		||||
                                />
 | 
			
		||||
                            </>
 | 
			
		||||
                                }
 | 
			
		||||
                            />
 | 
			
		||||
                        )}
 | 
			
		||||
                    </>
 | 
			
		||||
                    }
 | 
			
		||||
                    elseShow={
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <FeatureStrategyEmpty
 | 
			
		||||
                        projectId={projectId}
 | 
			
		||||
                        featureId={featureId}
 | 
			
		||||
                        environmentId={featureEnvironment.name}
 | 
			
		||||
                    />
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
                )}
 | 
			
		||||
            </StyledAccordionBodyInnerContainer>
 | 
			
		||||
        </StyledAccordionBody>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ import { Box, styled } from '@mui/material';
 | 
			
		||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
			
		||||
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
 | 
			
		||||
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
 | 
			
		||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
			
		||||
import { FeatureOverviewEnvironmentBody } from './FeatureOverviewEnvironmentBody';
 | 
			
		||||
import FeatureOverviewEnvironmentMetrics from '../FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
 | 
			
		||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
 | 
			
		||||
@ -13,6 +12,7 @@ const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
 | 
			
		||||
    padding: theme.spacing(1, 3),
 | 
			
		||||
    borderRadius: theme.shape.borderRadiusLarge,
 | 
			
		||||
    backgroundColor: theme.palette.background.paper,
 | 
			
		||||
    border: `1px solid ${theme.palette.divider}`,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledFeatureOverviewEnvironmentBody = styled(
 | 
			
		||||
@ -52,18 +52,35 @@ const StyledHeaderTitle = styled('span')(({ theme }) => ({
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
interface INewFeatureOverviewEnvironmentProps {
 | 
			
		||||
    environmentId: string;
 | 
			
		||||
    hiddenEnvironments: string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const NewFeatureOverviewEnvironment = ({
 | 
			
		||||
    environmentId,
 | 
			
		||||
export const FeatureOverviewEnvironment = ({
 | 
			
		||||
    hiddenEnvironments,
 | 
			
		||||
}: INewFeatureOverviewEnvironmentProps) => {
 | 
			
		||||
    const projectId = useRequiredPathParam('projectId');
 | 
			
		||||
    const featureId = useRequiredPathParam('featureId');
 | 
			
		||||
    const { metrics } = useFeatureMetrics(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(
 | 
			
		||||
            ({ environment }) => environment === environmentId,
 | 
			
		||||
        );
 | 
			
		||||
@ -71,15 +88,12 @@ export const NewFeatureOverviewEnvironment = ({
 | 
			
		||||
            ({ name }) => name === environmentId,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    if (!featureEnvironment)
 | 
			
		||||
        return (
 | 
			
		||||
            <StyledFeatureOverviewEnvironment className='skeleton'>
 | 
			
		||||
                <Box sx={{ height: '400px' }} />
 | 
			
		||||
            </StyledFeatureOverviewEnvironment>
 | 
			
		||||
        );
 | 
			
		||||
        if (!featureEnvironment) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
        <StyledFeatureOverviewEnvironment>
 | 
			
		||||
            <StyledFeatureOverviewEnvironment key={environmentId}>
 | 
			
		||||
                <StyledHeader data-loading>
 | 
			
		||||
                    <StyledHeaderToggleContainer>
 | 
			
		||||
                        <FeatureOverviewEnvironmentToggle
 | 
			
		||||
@ -89,7 +103,9 @@ export const NewFeatureOverviewEnvironment = ({
 | 
			
		||||
                            <StyledHeaderTitleLabel>
 | 
			
		||||
                                Environment
 | 
			
		||||
                            </StyledHeaderTitleLabel>
 | 
			
		||||
                        <StyledHeaderTitle>{environmentId}</StyledHeaderTitle>
 | 
			
		||||
                            <StyledHeaderTitle>
 | 
			
		||||
                                {environmentId}
 | 
			
		||||
                            </StyledHeaderTitle>
 | 
			
		||||
                        </StyledHeaderTitleContainer>
 | 
			
		||||
                    </StyledHeaderToggleContainer>
 | 
			
		||||
                    <FeatureOverviewEnvironmentMetrics
 | 
			
		||||
@ -97,7 +113,6 @@ export const NewFeatureOverviewEnvironment = ({
 | 
			
		||||
                        disabled={!featureEnvironment.enabled}
 | 
			
		||||
                    />
 | 
			
		||||
                </StyledHeader>
 | 
			
		||||
 | 
			
		||||
                <StyledFeatureOverviewEnvironmentBody
 | 
			
		||||
                    featureEnvironment={featureEnvironment}
 | 
			
		||||
                    isDisabled={!featureEnvironment.enabled}
 | 
			
		||||
@ -105,9 +120,7 @@ export const NewFeatureOverviewEnvironment = ({
 | 
			
		||||
                        .map(({ name }) => name)
 | 
			
		||||
                        .filter((name) => name !== environmentId)}
 | 
			
		||||
                />
 | 
			
		||||
            <ConditionallyRender
 | 
			
		||||
                condition={(featureEnvironment?.strategies?.length || 0) > 0}
 | 
			
		||||
                show={
 | 
			
		||||
                {featureEnvironment?.strategies?.length > 0 ? (
 | 
			
		||||
                    <>
 | 
			
		||||
                        <Box
 | 
			
		||||
                            sx={{
 | 
			
		||||
@ -124,8 +137,8 @@ export const NewFeatureOverviewEnvironment = ({
 | 
			
		||||
                            />
 | 
			
		||||
                        </Box>
 | 
			
		||||
                    </>
 | 
			
		||||
                }
 | 
			
		||||
            />
 | 
			
		||||
                ) : null}
 | 
			
		||||
            </StyledFeatureOverviewEnvironment>
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { Link, Route, Routes } from 'react-router-dom';
 | 
			
		||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
 | 
			
		||||
import FeatureLog from './FeatureLog/FeatureLog';
 | 
			
		||||
import FeatureOverview from './FeatureOverview/FeatureOverview';
 | 
			
		||||
import { FeatureOverview } from './FeatureOverview/FeatureOverview';
 | 
			
		||||
import { FeatureEnvironmentVariants } from './FeatureVariants/FeatureEnvironmentVariants/FeatureEnvironmentVariants';
 | 
			
		||||
import { FeatureMetrics } from './FeatureMetrics/FeatureMetrics';
 | 
			
		||||
import { FeatureSettings } from './FeatureSettings/FeatureSettings';
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,9 @@ interface IGlobalStore {
 | 
			
		||||
    hiddenEnvironments?: Array<string>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @deprecated use tested `useLocalStorageState` hook instead
 | 
			
		||||
 */
 | 
			
		||||
export const useGlobalLocalStorage = () => {
 | 
			
		||||
    const { value, setValue } = createLocalStorage<IGlobalStore>(
 | 
			
		||||
        'global:v1',
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,9 @@ import { useGlobalLocalStorage } from './useGlobalLocalStorage';
 | 
			
		||||
import { useState } from 'react';
 | 
			
		||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @deprecated remove with `flagOverviewRedesign`
 | 
			
		||||
 */
 | 
			
		||||
export const useHiddenEnvironments = () => {
 | 
			
		||||
    const { trackEvent } = usePlausibleTracker();
 | 
			
		||||
 | 
			
		||||
@ -25,11 +28,6 @@ export const useHiddenEnvironments = () => {
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                hiddenEnvironments.add(environment);
 | 
			
		||||
                trackEvent('hidden_environment', {
 | 
			
		||||
                    props: {
 | 
			
		||||
                        eventType: `environment hidden`,
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            setStoredHiddenEnvironments(hiddenEnvironments);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user