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 | ||||
|                             hiddenEnvironments={hiddenEnvironments} | ||||
|                             setHiddenEnvironments={setHiddenEnvironments} | ||||
|                         /> | ||||
|                     </> | ||||
|                 )} | ||||
|                 <FeatureOverviewMetaData | ||||
|                     hiddenEnvironments={hiddenEnvironments} | ||||
|                     onEnvironmentVisibilityChange={ | ||||
|                         onEnvironmentVisibilityChange | ||||
|                     } | ||||
|                 /> | ||||
|             </div> | ||||
|             <StyledMainContent> | ||||
|                 {flagOverviewRedesign ? ( | ||||
|                     <NewFeatureOverviewEnvironment | ||||
|                         environmentId={environmentId} | ||||
|                     /> | ||||
|                 ) : ( | ||||
|                     <FeatureOverviewEnvironments /> | ||||
|                 )} | ||||
|                 <FeatureOverviewEnvironment | ||||
|                     hiddenEnvironments={hiddenEnvironments} | ||||
|                 /> | ||||
|             </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,132 +229,91 @@ export const FeatureOverviewEnvironmentBody = ({ | ||||
|     return ( | ||||
|         <StyledAccordionBody> | ||||
|             <StyledAccordionBodyInnerContainer> | ||||
|                 <ConditionallyRender | ||||
|                     condition={ | ||||
|                         (releasePlans.length > 0 || | ||||
|                             strategiesToDisplay.length > 0) && | ||||
|                         isDisabled | ||||
|                     } | ||||
|                     show={() => ( | ||||
|                         <Alert severity='warning' sx={{ mb: 2 }}> | ||||
|                             This environment is disabled, which means that none | ||||
|                             of your strategies are executing. | ||||
|                         </Alert> | ||||
|                     )} | ||||
|                 /> | ||||
|                 <ConditionallyRender | ||||
|                     condition={ | ||||
|                         releasePlans.length > 0 || | ||||
|                         strategiesToDisplay.length > 0 | ||||
|                     } | ||||
|                     show={ | ||||
|                         <> | ||||
|                             {releasePlans.map((plan) => ( | ||||
|                                 <ReleasePlan | ||||
|                                     key={plan.id} | ||||
|                                     plan={plan} | ||||
|                                     environmentIsDisabled={isDisabled} | ||||
|                 {(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. | ||||
|                     </Alert> | ||||
|                 ) : null} | ||||
|                 {releasePlans.length > 0 || strategiesToDisplay.length > 0 ? ( | ||||
|                     <> | ||||
|                         {releasePlans.map((plan) => ( | ||||
|                             <ReleasePlan | ||||
|                                 key={plan.id} | ||||
|                                 plan={plan} | ||||
|                                 environmentIsDisabled={isDisabled} | ||||
|                             /> | ||||
|                         ))} | ||||
|                         {releasePlans.length > 0 && strategies.length > 0 ? ( | ||||
|                             <SectionSeparator> | ||||
|                                 <StyledBadge>OR</StyledBadge> | ||||
|                             </SectionSeparator> | ||||
|                         ) : null} | ||||
|                         {strategiesToDisplay.length < 50 || | ||||
|                         !manyStrategiesPagination ? ( | ||||
|                             <> | ||||
|                                 {strategiesToDisplay.map((strategy, index) => ( | ||||
|                                     <StrategyDraggableItem | ||||
|                                         key={strategy.id} | ||||
|                                         strategy={strategy} | ||||
|                                         index={index} | ||||
|                                         environmentName={ | ||||
|                                             featureEnvironment.name | ||||
|                                         } | ||||
|                                         otherEnvironments={otherEnvironments} | ||||
|                                         isDragging={ | ||||
|                                             dragItem?.id === strategy.id | ||||
|                                         } | ||||
|                                         onDragStartRef={onDragStartRef} | ||||
|                                         onDragOver={onDragOver(strategy.id)} | ||||
|                                         onDragEnd={onDragEnd} | ||||
|                                     /> | ||||
|                                 ))} | ||||
|                             </> | ||||
|                         ) : ( | ||||
|                             <> | ||||
|                                 <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. | ||||
|                                 </Alert> | ||||
|                                 <br /> | ||||
|                                 {page.map((strategy, index) => ( | ||||
|                                     <StrategyDraggableItem | ||||
|                                         key={strategy.id} | ||||
|                                         strategy={strategy} | ||||
|                                         index={index + pageIndex * pageSize} | ||||
|                                         environmentName={ | ||||
|                                             featureEnvironment.name | ||||
|                                         } | ||||
|                                         otherEnvironments={otherEnvironments} | ||||
|                                         isDragging={false} | ||||
|                                         onDragStartRef={(() => {}) as any} | ||||
|                                         onDragOver={(() => {}) as any} | ||||
|                                         onDragEnd={(() => {}) as any} | ||||
|                                     /> | ||||
|                                 ))} | ||||
|                                 <br /> | ||||
|                                 <Pagination | ||||
|                                     count={pages.length} | ||||
|                                     shape='rounded' | ||||
|                                     page={pageIndex + 1} | ||||
|                                     onChange={(_, page) => | ||||
|                                         setPageIndex(page - 1) | ||||
|                                     } | ||||
|                                 /> | ||||
|                             ))} | ||||
|                             <ConditionallyRender | ||||
|                                 condition={ | ||||
|                                     releasePlans.length > 0 && | ||||
|                                     strategies.length > 0 | ||||
|                                 } | ||||
|                                 show={ | ||||
|                                     <SectionSeparator> | ||||
|                                         <StyledBadge>OR</StyledBadge> | ||||
|                                     </SectionSeparator> | ||||
|                                 } | ||||
|                             /> | ||||
|                             <ConditionallyRender | ||||
|                                 condition={ | ||||
|                                     strategiesToDisplay.length < 50 || | ||||
|                                     !manyStrategiesPagination | ||||
|                                 } | ||||
|                                 show={ | ||||
|                                     <> | ||||
|                                         {strategiesToDisplay.map( | ||||
|                                             (strategy, index) => ( | ||||
|                                                 <StrategyDraggableItem | ||||
|                                                     key={strategy.id} | ||||
|                                                     strategy={strategy} | ||||
|                                                     index={index} | ||||
|                                                     environmentName={ | ||||
|                                                         featureEnvironment.name | ||||
|                                                     } | ||||
|                                                     otherEnvironments={ | ||||
|                                                         otherEnvironments | ||||
|                                                     } | ||||
|                                                     isDragging={ | ||||
|                                                         dragItem?.id === | ||||
|                                                         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. | ||||
|                                         </Alert> | ||||
|                                         <br /> | ||||
|                                         {page.map((strategy, index) => ( | ||||
|                                             <StrategyDraggableItem | ||||
|                                                 key={strategy.id} | ||||
|                                                 strategy={strategy} | ||||
|                                                 index={ | ||||
|                                                     index + pageIndex * pageSize | ||||
|                                                 } | ||||
|                                                 environmentName={ | ||||
|                                                     featureEnvironment.name | ||||
|                                                 } | ||||
|                                                 otherEnvironments={ | ||||
|                                                     otherEnvironments | ||||
|                                                 } | ||||
|                                                 isDragging={false} | ||||
|                                                 onDragStartRef={ | ||||
|                                                     (() => {}) as any | ||||
|                                                 } | ||||
|                                                 onDragOver={(() => {}) as any} | ||||
|                                                 onDragEnd={(() => {}) as any} | ||||
|                                             /> | ||||
|                                         ))} | ||||
|                                         <br /> | ||||
|                                         <Pagination | ||||
|                                             count={pages.length} | ||||
|                                             shape='rounded' | ||||
|                                             page={pageIndex + 1} | ||||
|                                             onChange={(_, page) => | ||||
|                                                 setPageIndex(page - 1) | ||||
|                                             } | ||||
|                                         /> | ||||
|                                     </> | ||||
|                                 } | ||||
|                             /> | ||||
|                         </> | ||||
|                     } | ||||
|                     elseShow={ | ||||
|                         <FeatureStrategyEmpty | ||||
|                             projectId={projectId} | ||||
|                             featureId={featureId} | ||||
|                             environmentId={featureEnvironment.name} | ||||
|                         /> | ||||
|                     } | ||||
|                 /> | ||||
|                             </> | ||||
|                         )} | ||||
|                     </> | ||||
|                 ) : ( | ||||
|                     <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,62 +52,75 @@ 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 environmentMetric = featureMetrics.find( | ||||
|         ({ environment }) => environment === environmentId, | ||||
|     ); | ||||
|     const featureEnvironment = feature?.environments.find( | ||||
|         ({ name }) => name === environmentId, | ||||
|     ); | ||||
|     const environments = | ||||
|         feature?.environments.filter( | ||||
|             ({ name }) => !hiddenEnvironments.includes(name), | ||||
|         ) || []; | ||||
| 
 | ||||
|     if (!featureEnvironment) | ||||
|     if (!environments || environments.length === 0) { | ||||
|         return ( | ||||
|             <StyledFeatureOverviewEnvironment className='skeleton'> | ||||
|                 <Box sx={{ height: '400px' }} /> | ||||
|             </StyledFeatureOverviewEnvironment> | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledFeatureOverviewEnvironment> | ||||
|             <StyledHeader data-loading> | ||||
|                 <StyledHeaderToggleContainer> | ||||
|                     <FeatureOverviewEnvironmentToggle | ||||
|                         environment={featureEnvironment} | ||||
|     return environments.map(({ name: environmentId }) => { | ||||
|         const featureMetrics = getFeatureMetrics( | ||||
|             feature?.environments, | ||||
|             metrics, | ||||
|         ); | ||||
|         const environmentMetric = featureMetrics.find( | ||||
|             ({ environment }) => environment === environmentId, | ||||
|         ); | ||||
|         const featureEnvironment = feature?.environments.find( | ||||
|             ({ name }) => name === environmentId, | ||||
|         ); | ||||
| 
 | ||||
|         if (!featureEnvironment) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <StyledFeatureOverviewEnvironment key={environmentId}> | ||||
|                 <StyledHeader data-loading> | ||||
|                     <StyledHeaderToggleContainer> | ||||
|                         <FeatureOverviewEnvironmentToggle | ||||
|                             environment={featureEnvironment} | ||||
|                         /> | ||||
|                         <StyledHeaderTitleContainer> | ||||
|                             <StyledHeaderTitleLabel> | ||||
|                                 Environment | ||||
|                             </StyledHeaderTitleLabel> | ||||
|                             <StyledHeaderTitle> | ||||
|                                 {environmentId} | ||||
|                             </StyledHeaderTitle> | ||||
|                         </StyledHeaderTitleContainer> | ||||
|                     </StyledHeaderToggleContainer> | ||||
|                     <FeatureOverviewEnvironmentMetrics | ||||
|                         environmentMetric={environmentMetric} | ||||
|                         disabled={!featureEnvironment.enabled} | ||||
|                     /> | ||||
|                     <StyledHeaderTitleContainer> | ||||
|                         <StyledHeaderTitleLabel> | ||||
|                             Environment | ||||
|                         </StyledHeaderTitleLabel> | ||||
|                         <StyledHeaderTitle>{environmentId}</StyledHeaderTitle> | ||||
|                     </StyledHeaderTitleContainer> | ||||
|                 </StyledHeaderToggleContainer> | ||||
|                 <FeatureOverviewEnvironmentMetrics | ||||
|                     environmentMetric={environmentMetric} | ||||
|                     disabled={!featureEnvironment.enabled} | ||||
|                 </StyledHeader> | ||||
|                 <StyledFeatureOverviewEnvironmentBody | ||||
|                     featureEnvironment={featureEnvironment} | ||||
|                     isDisabled={!featureEnvironment.enabled} | ||||
|                     otherEnvironments={feature?.environments | ||||
|                         .map(({ name }) => name) | ||||
|                         .filter((name) => name !== environmentId)} | ||||
|                 /> | ||||
|             </StyledHeader> | ||||
| 
 | ||||
|             <StyledFeatureOverviewEnvironmentBody | ||||
|                 featureEnvironment={featureEnvironment} | ||||
|                 isDisabled={!featureEnvironment.enabled} | ||||
|                 otherEnvironments={feature?.environments | ||||
|                     .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> | ||||
|                     </> | ||||
|                 } | ||||
|             /> | ||||
|         </StyledFeatureOverviewEnvironment> | ||||
|     ); | ||||
|                 ) : 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