mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: add a suggestion banner at the bottom of empty feature-environments (#10725)
This commit is contained in:
		
							parent
							
								
									c65a336783
								
							
						
					
					
						commit
						c39b4cd1b0
					
				| @ -1,4 +1,4 @@ | ||||
| import type { FC, PropsWithChildren } from 'react'; | ||||
| import { useMemo, type FC, type PropsWithChildren } from 'react'; | ||||
| import { | ||||
|     AccordionSummary, | ||||
|     type AccordionSummaryProps, | ||||
| @ -7,12 +7,16 @@ import { | ||||
| import ExpandMore from '@mui/icons-material/ExpandMore'; | ||||
| import { Truncator } from 'component/common/Truncator/Truncator'; | ||||
| import { useId } from 'hooks/useId'; | ||||
| import { EnvironmentStrategySuggestion } from './EnvironmentStrategySuggestion/EnvironmentStrategySuggestion.js'; | ||||
| import type { IFeatureStrategy } from 'interfaces/strategy'; | ||||
| import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments'; | ||||
| 
 | ||||
| const StyledAccordionSummary = styled(AccordionSummary, { | ||||
|     shouldForwardProp: (prop) => prop !== 'expandable', | ||||
|     shouldForwardProp: (prop) => prop !== 'expandable' && prop !== 'empty', | ||||
| })<{ | ||||
|     expandable?: boolean; | ||||
| }>(({ theme, expandable }) => ({ | ||||
|     empty?: boolean; | ||||
| }>(({ theme, expandable, empty }) => ({ | ||||
|     boxShadow: 'none', | ||||
|     padding: theme.spacing(0.5, 3, 0.5, 2), | ||||
|     display: 'flex', | ||||
| @ -27,9 +31,26 @@ const StyledAccordionSummary = styled(AccordionSummary, { | ||||
|     ':focus-within': { | ||||
|         background: 'none', | ||||
|     }, | ||||
|     ...(empty && { | ||||
|         padding: 0, | ||||
|         alignItems: 'normal', | ||||
|         '.MuiAccordionSummary-content': { | ||||
|             marginBottom: '0px', | ||||
|             paddingBottom: '0px', | ||||
|             flexDirection: 'column', | ||||
|         }, | ||||
| 
 | ||||
|         '.MuiAccordionSummary-expandIconWrapper': { | ||||
|             width: '0px', | ||||
|         }, | ||||
|     }), | ||||
| })); | ||||
| 
 | ||||
| const StyledHeader = styled('header')(({ theme }) => ({ | ||||
| const StyledHeader = styled('header', { | ||||
|     shouldForwardProp: (prop) => prop !== 'empty', | ||||
| })<{ | ||||
|     empty?: boolean; | ||||
| }>(({ theme, empty }) => ({ | ||||
|     display: 'flex', | ||||
|     columnGap: theme.spacing(1), | ||||
|     paddingRight: theme.spacing(1), | ||||
| @ -37,6 +58,9 @@ const StyledHeader = styled('header')(({ theme }) => ({ | ||||
|     color: theme.palette.text.primary, | ||||
|     alignItems: 'center', | ||||
|     minHeight: theme.spacing(8), | ||||
|     ...(empty && { | ||||
|         padding: theme.spacing(0, 8, 0, 2), | ||||
|     }), | ||||
| })); | ||||
| 
 | ||||
| const StyledHeaderTitle = styled('hgroup')(({ theme }) => ({ | ||||
| @ -79,9 +103,12 @@ type EnvironmentMetadata = { | ||||
| }; | ||||
| 
 | ||||
| type EnvironmentHeaderProps = { | ||||
|     projectId: string; | ||||
|     featureId: string; | ||||
|     environmentId: string; | ||||
|     expandable?: boolean; | ||||
|     environmentMetadata?: EnvironmentMetadata; | ||||
|     hasActivations?: boolean; | ||||
| } & AccordionSummaryProps; | ||||
| 
 | ||||
| const MetadataChip = ({ | ||||
| @ -110,19 +137,53 @@ const MetadataChip = ({ | ||||
|     return <StyledStrategyCount>{text}</StyledStrategyCount>; | ||||
| }; | ||||
| 
 | ||||
| const DEFAULT_STRATEGY: Omit<IFeatureStrategy, 'id'> = { | ||||
|     name: 'flexibleRollout', | ||||
|     disabled: false, | ||||
|     constraints: [], | ||||
|     title: '', | ||||
|     parameters: { | ||||
|         rollout: '100', | ||||
|         stickiness: 'default', | ||||
|         groupId: '', | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| export const environmentAccordionSummaryClassName = | ||||
|     'environment-accordion-summary'; | ||||
| 
 | ||||
| export const EnvironmentHeader: FC< | ||||
|     PropsWithChildren<EnvironmentHeaderProps> | ||||
| > = ({ | ||||
|     projectId, | ||||
|     featureId, | ||||
|     environmentId, | ||||
|     children, | ||||
|     expandable = true, | ||||
|     environmentMetadata, | ||||
|     hasActivations = false, | ||||
|     ...props | ||||
| }) => { | ||||
|     const id = useId(); | ||||
|     const { environments } = useProjectEnvironments(projectId); | ||||
|     const defaultStrategy = environments.find( | ||||
|         (env) => env.name === environmentId, | ||||
|     )?.defaultStrategy; | ||||
| 
 | ||||
|     const strategy: Omit<IFeatureStrategy, 'id'> = useMemo(() => { | ||||
|         const baseDefaultStrategy = { | ||||
|             ...DEFAULT_STRATEGY, | ||||
|             ...defaultStrategy, | ||||
|         }; | ||||
|         return { | ||||
|             ...baseDefaultStrategy, | ||||
|             disabled: false, | ||||
|             constraints: baseDefaultStrategy.constraints ?? [], | ||||
|             title: baseDefaultStrategy.title ?? '', | ||||
|             parameters: baseDefaultStrategy.parameters ?? {}, | ||||
|         }; | ||||
|     }, [JSON.stringify(defaultStrategy)]); | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledAccordionSummary | ||||
|             {...props} | ||||
| @ -136,8 +197,9 @@ export const EnvironmentHeader: FC< | ||||
|             expandable={expandable} | ||||
|             tabIndex={expandable ? 0 : -1} | ||||
|             className={environmentAccordionSummaryClassName} | ||||
|             empty={!hasActivations} | ||||
|         > | ||||
|             <StyledHeader data-loading> | ||||
|             <StyledHeader empty={!hasActivations} data-loading> | ||||
|                 <StyledHeaderTitle> | ||||
|                     <StyledHeaderTitleLabel>Environment</StyledHeaderTitleLabel> | ||||
|                     <StyledTruncator component='h2'> | ||||
| @ -149,6 +211,14 @@ export const EnvironmentHeader: FC< | ||||
|                 </StyledHeaderTitle> | ||||
|                 {children} | ||||
|             </StyledHeader> | ||||
|             {!hasActivations && ( | ||||
|                 <EnvironmentStrategySuggestion | ||||
|                     projectId={projectId} | ||||
|                     featureId={featureId} | ||||
|                     environmentId={environmentId} | ||||
|                     strategy={strategy} | ||||
|                 /> | ||||
|             )} | ||||
|         </StyledAccordionSummary> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -0,0 +1,115 @@ | ||||
| import { Box, styled } from '@mui/material'; | ||||
| import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip'; | ||||
| import { Link, useNavigate } from 'react-router-dom'; | ||||
| import { StrategyExecution } from '../../EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution.js'; | ||||
| import PermissionButton from 'component/common/PermissionButton/PermissionButton.js'; | ||||
| import { usePlausibleTracker } from 'hooks/usePlausibleTracker.js'; | ||||
| import { formatCreateStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.js'; | ||||
| import { UPDATE_FEATURE } from '@server/types/permissions.js'; | ||||
| import type { IFeatureStrategy } from 'interfaces/strategy.js'; | ||||
| 
 | ||||
| const StyledSuggestion = styled('div')(({ theme }) => ({ | ||||
|     width: '100%', | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
|     padding: theme.spacing(0.5, 3), | ||||
|     background: theme.palette.secondary.light, | ||||
|     borderBottomLeftRadius: theme.shape.borderRadiusLarge, | ||||
|     borderBottomRightRadius: theme.shape.borderRadiusLarge, | ||||
|     color: theme.palette.primary.main, | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
| })); | ||||
| 
 | ||||
| const StyledBold = styled('b')(({ theme }) => ({ | ||||
|     fontWeight: theme.typography.fontWeightBold, | ||||
| })); | ||||
| 
 | ||||
| const StyledSpan = styled('span')(({ theme }) => ({ | ||||
|     fontWeight: theme.typography.fontWeightBold, | ||||
|     textDecoration: 'underline', | ||||
| })); | ||||
| 
 | ||||
| const TooltipHeader = styled('div')(({ theme }) => ({ | ||||
|     fontWeight: theme.typography.fontWeightBold, | ||||
| })); | ||||
| 
 | ||||
| const TooltipDescription = styled('div')(({ theme }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|     paddingBottom: theme.spacing(1.5), | ||||
| })); | ||||
| 
 | ||||
| const StyledBox = styled(Box)(({ theme }) => ({ | ||||
|     padding: theme.spacing(1.5), | ||||
| })); | ||||
| 
 | ||||
| type DefaultStrategySuggestionProps = { | ||||
|     projectId: string; | ||||
|     featureId: string; | ||||
|     environmentId: string; | ||||
|     strategy: Omit<IFeatureStrategy, 'id'>; | ||||
| }; | ||||
| 
 | ||||
| export const EnvironmentStrategySuggestion = ({ | ||||
|     projectId, | ||||
|     featureId, | ||||
|     environmentId, | ||||
|     strategy, | ||||
| }: DefaultStrategySuggestionProps) => { | ||||
|     const { trackEvent } = usePlausibleTracker(); | ||||
|     const navigate = useNavigate(); | ||||
|     const editDefaultStrategyPath = `/projects/${projectId}/settings/default-strategy`; | ||||
|     const createStrategyPath = formatCreateStrategyPath( | ||||
|         projectId, | ||||
|         featureId, | ||||
|         environmentId, | ||||
|         'flexibleRollout', | ||||
|         true, | ||||
|     ); | ||||
| 
 | ||||
|     const openStrategyCreationModal = () => { | ||||
|         trackEvent('suggestion-strategy-add', { | ||||
|             props: { | ||||
|                 buttonTitle: 'flexibleRollout', | ||||
|             }, | ||||
|         }); | ||||
|         navigate(createStrategyPath); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledSuggestion> | ||||
|             <StyledBold>Suggestion:</StyledBold> | ||||
|              Add the  | ||||
|             <HtmlTooltip | ||||
|                 title={ | ||||
|                     <StyledBox> | ||||
|                         <TooltipHeader>Default strategy</TooltipHeader> | ||||
|                         <TooltipDescription> | ||||
|                             Defined per project, per environment  | ||||
|                             <Link | ||||
|                                 to={editDefaultStrategyPath} | ||||
|                                 title='Project default strategies' | ||||
|                             > | ||||
|                                 here | ||||
|                             </Link> | ||||
|                         </TooltipDescription> | ||||
|                         <StrategyExecution strategy={strategy} /> | ||||
|                     </StyledBox> | ||||
|                 } | ||||
|                 maxWidth='200' | ||||
|                 arrow | ||||
|             > | ||||
|                 <StyledSpan>default strategy</StyledSpan> | ||||
|             </HtmlTooltip> | ||||
|              for this project  | ||||
|             <PermissionButton | ||||
|                 size='small' | ||||
|                 permission={UPDATE_FEATURE} | ||||
|                 projectId={projectId} | ||||
|                 variant='text' | ||||
|                 onClick={() => openStrategyCreationModal()} | ||||
|             > | ||||
|                 Apply | ||||
|             </PermissionButton> | ||||
|         </StyledSuggestion> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,154 @@ | ||||
| import type { FC, PropsWithChildren } from 'react'; | ||||
| import { | ||||
|     AccordionSummary, | ||||
|     type AccordionSummaryProps, | ||||
|     styled, | ||||
| } from '@mui/material'; | ||||
| import ExpandMore from '@mui/icons-material/ExpandMore'; | ||||
| import { Truncator } from 'component/common/Truncator/Truncator'; | ||||
| import { useId } from 'hooks/useId'; | ||||
| 
 | ||||
| const StyledAccordionSummary = styled(AccordionSummary, { | ||||
|     shouldForwardProp: (prop) => prop !== 'expandable', | ||||
| })<{ | ||||
|     expandable?: boolean; | ||||
| }>(({ theme, expandable }) => ({ | ||||
|     boxShadow: 'none', | ||||
|     padding: theme.spacing(0.5, 3, 0.5, 2), | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
|     borderRadius: theme.shape.borderRadiusLarge, | ||||
|     pointerEvents: 'auto', | ||||
|     opacity: 1, | ||||
|     '&&&': { | ||||
|         cursor: expandable ? 'pointer' : 'default', | ||||
|     }, | ||||
| 
 | ||||
|     ':focus-within': { | ||||
|         background: 'none', | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const StyledHeader = styled('header')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     columnGap: theme.spacing(1), | ||||
|     paddingRight: theme.spacing(1), | ||||
|     width: '100%', | ||||
|     color: theme.palette.text.primary, | ||||
|     alignItems: 'center', | ||||
|     minHeight: theme.spacing(8), | ||||
| })); | ||||
| 
 | ||||
| const StyledHeaderTitle = styled('hgroup')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     flexFlow: 'row wrap', | ||||
|     flex: 1, | ||||
|     columnGap: theme.spacing(1), | ||||
| })); | ||||
| 
 | ||||
| const StyledHeaderTitleLabel = styled('p')(({ theme }) => ({ | ||||
|     width: '100%', | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|     color: theme.palette.text.secondary, | ||||
| })); | ||||
| 
 | ||||
| const StyledTruncator = styled(Truncator)(({ theme }) => ({ | ||||
|     fontSize: theme.typography.h2.fontSize, | ||||
|     fontWeight: theme.typography.fontWeightMedium, | ||||
| })); | ||||
| 
 | ||||
| const StyledStrategyCount = styled('p')(({ theme }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|     color: theme.palette.info.contrastText, | ||||
|     backgroundColor: theme.palette.info.light, | ||||
|     whiteSpace: 'nowrap', | ||||
|     width: 'min-content', | ||||
|     borderRadius: theme.shape.borderRadiusExtraLarge, | ||||
|     padding: theme.spacing(0.5, 1), | ||||
| })); | ||||
| 
 | ||||
| const NeutralStrategyCount = styled(StyledStrategyCount)(({ theme }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|     color: theme.palette.text.secondary, | ||||
|     backgroundColor: theme.palette.neutral.light, | ||||
| })); | ||||
| 
 | ||||
| type EnvironmentMetadata = { | ||||
|     strategyCount: number; | ||||
|     releasePlanCount: number; | ||||
| }; | ||||
| 
 | ||||
| type EnvironmentHeaderProps = { | ||||
|     environmentId: string; | ||||
|     expandable?: boolean; | ||||
|     environmentMetadata?: EnvironmentMetadata; | ||||
| } & AccordionSummaryProps; | ||||
| 
 | ||||
| const MetadataChip = ({ | ||||
|     strategyCount, | ||||
|     releasePlanCount, | ||||
| }: EnvironmentMetadata) => { | ||||
|     if (strategyCount === 0 && releasePlanCount === 0) { | ||||
|         return <NeutralStrategyCount>0 strategies added</NeutralStrategyCount>; | ||||
|     } | ||||
| 
 | ||||
|     const releasePlanText = releasePlanCount > 0 ? 'Release plan' : undefined; | ||||
| 
 | ||||
|     const strategyText = () => { | ||||
|         switch (strategyCount) { | ||||
|             case 0: | ||||
|                 return undefined; | ||||
|             case 1: | ||||
|                 return `1 strategy`; | ||||
|             default: | ||||
|                 return `${strategyCount} strategies`; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const text = `${[releasePlanText, strategyText()].filter(Boolean).join(', ')} added`; | ||||
| 
 | ||||
|     return <StyledStrategyCount>{text}</StyledStrategyCount>; | ||||
| }; | ||||
| 
 | ||||
| export const environmentAccordionSummaryClassName = | ||||
|     'environment-accordion-summary'; | ||||
| 
 | ||||
| export const LegacyEnvironmentHeader: FC< | ||||
|     PropsWithChildren<EnvironmentHeaderProps> | ||||
| > = ({ | ||||
|     environmentId, | ||||
|     children, | ||||
|     expandable = true, | ||||
|     environmentMetadata, | ||||
|     ...props | ||||
| }) => { | ||||
|     const id = useId(); | ||||
|     return ( | ||||
|         <StyledAccordionSummary | ||||
|             {...props} | ||||
|             expandIcon={ | ||||
|                 <ExpandMore | ||||
|                     sx={{ visibility: expandable ? 'visible' : 'hidden' }} | ||||
|                 /> | ||||
|             } | ||||
|             id={id} | ||||
|             aria-controls={`environment-accordion-${id}-content`} | ||||
|             expandable={expandable} | ||||
|             tabIndex={expandable ? 0 : -1} | ||||
|             className={environmentAccordionSummaryClassName} | ||||
|         > | ||||
|             <StyledHeader data-loading> | ||||
|                 <StyledHeaderTitle> | ||||
|                     <StyledHeaderTitleLabel>Environment</StyledHeaderTitleLabel> | ||||
|                     <StyledTruncator component='h2'> | ||||
|                         {environmentId} | ||||
|                     </StyledTruncator> | ||||
|                     {environmentMetadata ? ( | ||||
|                         <MetadataChip {...environmentMetadata} /> | ||||
|                     ) : null} | ||||
|                 </StyledHeaderTitle> | ||||
|                 {children} | ||||
|             </StyledHeader> | ||||
|         </StyledAccordionSummary> | ||||
|     ); | ||||
| }; | ||||
| @ -29,6 +29,8 @@ const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({ | ||||
| const StyledAccordion = styled(Accordion)(({ theme }) => ({ | ||||
|     boxShadow: 'none', | ||||
|     background: 'none', | ||||
|     borderRadius: theme.shape.borderRadiusLarge, | ||||
| 
 | ||||
|     [`&:has(.${environmentAccordionSummaryClassName}:focus-visible)`]: { | ||||
|         background: theme.palette.table.headerHover, | ||||
|     }, | ||||
| @ -97,7 +99,10 @@ export const FeatureOverviewEnvironment = ({ | ||||
|                         releasePlanCount: environment.releasePlans?.length ?? 0, | ||||
|                     }} | ||||
|                     environmentId={environment.name} | ||||
|                     projectId={projectId} | ||||
|                     featureId={featureId} | ||||
|                     expandable={hasActivations} | ||||
|                     hasActivations={hasActivations} | ||||
|                 > | ||||
|                     <FeatureOverviewEnvironmentToggle | ||||
|                         environment={environment} | ||||
|  | ||||
| @ -0,0 +1,147 @@ | ||||
| import { Accordion, AccordionDetails, styled } from '@mui/material'; | ||||
| import type { | ||||
|     IFeatureEnvironment, | ||||
|     IFeatureEnvironmentMetrics, | ||||
| } from 'interfaces/featureToggle'; | ||||
| import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu'; | ||||
| import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import { UpgradeChangeRequests } from '../UpgradeChangeRequests/UpgradeChangeRequests.tsx'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { | ||||
|     environmentAccordionSummaryClassName, | ||||
|     LegacyEnvironmentHeader, | ||||
| } from '../EnvironmentHeader/LegacyEnvironmentHeader/LegacyEnvironmentHeader.tsx'; | ||||
| import FeatureOverviewEnvironmentMetrics from '../EnvironmentHeader/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx'; | ||||
| import { FeatureOverviewEnvironmentToggle } from '../EnvironmentHeader/FeatureOverviewEnvironmentToggle/FeatureOverviewEnvironmentToggle.tsx'; | ||||
| import { useState } from 'react'; | ||||
| import type { IReleasePlan } from 'interfaces/releasePlans'; | ||||
| import { EnvironmentAccordionBody } from '../EnvironmentAccordionBody/EnvironmentAccordionBody.tsx'; | ||||
| import { Box } from '@mui/material'; | ||||
| import { ReleaseTemplatesFeedback } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/ReleaseTemplatesFeedback/ReleaseTemplatesFeedback'; | ||||
| 
 | ||||
| const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({ | ||||
|     borderRadius: theme.shape.borderRadiusLarge, | ||||
|     backgroundColor: theme.palette.background.paper, | ||||
|     border: `1px solid ${theme.palette.divider}`, | ||||
| })); | ||||
| 
 | ||||
| const StyledAccordion = styled(Accordion)(({ theme }) => ({ | ||||
|     boxShadow: 'none', | ||||
|     background: 'none', | ||||
|     [`&:has(.${environmentAccordionSummaryClassName}:focus-visible)`]: { | ||||
|         background: theme.palette.table.headerHover, | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const NewStyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({ | ||||
|     padding: 0, | ||||
|     background: theme.palette.background.elevation1, | ||||
|     borderBottomLeftRadius: theme.shape.borderRadiusLarge, | ||||
|     borderBottomRightRadius: theme.shape.borderRadiusLarge, | ||||
|     boxShadow: theme.boxShadows.accordionFooter, | ||||
| })); | ||||
| 
 | ||||
| const StyledAccordionFooter = styled('footer')(({ theme }) => ({ | ||||
|     padding: theme.spacing(2, 3, 3), | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     gap: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| const StyledEnvironmentAccordionContainer = styled('div')(({ theme }) => ({ | ||||
|     width: '100%', | ||||
|     position: 'relative', | ||||
| })); | ||||
| 
 | ||||
| type FeatureOverviewEnvironmentProps = { | ||||
|     environment: IFeatureEnvironment & { | ||||
|         releasePlans?: IReleasePlan[]; | ||||
|     }; | ||||
|     metrics?: Pick<IFeatureEnvironmentMetrics, 'yes' | 'no'>; | ||||
|     otherEnvironments?: string[]; | ||||
|     onToggleEnvOpen?: (isOpen: boolean) => void; | ||||
| }; | ||||
| 
 | ||||
| export const LegacyFeatureOverviewEnvironment = ({ | ||||
|     environment, | ||||
|     metrics = { yes: 0, no: 0 }, | ||||
|     otherEnvironments = [], | ||||
|     onToggleEnvOpen = () => {}, | ||||
| }: FeatureOverviewEnvironmentProps) => { | ||||
|     const [isOpen, setIsOpen] = useState(false); | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
|     const featureId = useRequiredPathParam('featureId'); | ||||
|     const { isOss } = useUiConfig(); | ||||
|     const hasActivations = Boolean( | ||||
|         environment?.enabled || | ||||
|             (environment?.strategies && environment?.strategies.length > 0) || | ||||
|             (environment?.releasePlans && environment?.releasePlans.length > 0), | ||||
|     ); | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledFeatureOverviewEnvironment> | ||||
|             <StyledAccordion | ||||
|                 TransitionProps={{ mountOnEnter: true, unmountOnExit: true }} | ||||
|                 data-testid={`${FEATURE_ENVIRONMENT_ACCORDION}_${environment.name}`} | ||||
|                 expanded={isOpen && hasActivations} | ||||
|                 onChange={() => { | ||||
|                     const state = isOpen ? !isOpen : hasActivations; | ||||
|                     onToggleEnvOpen(state); | ||||
|                     setIsOpen(state); | ||||
|                 }} | ||||
|             > | ||||
|                 <LegacyEnvironmentHeader | ||||
|                     environmentMetadata={{ | ||||
|                         strategyCount: environment.strategies?.length ?? 0, | ||||
|                         releasePlanCount: environment.releasePlans?.length ?? 0, | ||||
|                     }} | ||||
|                     environmentId={environment.name} | ||||
|                     expandable={hasActivations} | ||||
|                 > | ||||
|                     <FeatureOverviewEnvironmentToggle | ||||
|                         environment={environment} | ||||
|                     /> | ||||
|                     {!hasActivations ? ( | ||||
|                         <FeatureStrategyMenu | ||||
|                             label='Add strategy' | ||||
|                             projectId={projectId} | ||||
|                             featureId={featureId} | ||||
|                             environmentId={environment.name} | ||||
|                             variant='outlined' | ||||
|                         /> | ||||
|                     ) : ( | ||||
|                         <FeatureOverviewEnvironmentMetrics | ||||
|                             environmentMetric={metrics} | ||||
|                         /> | ||||
|                     )} | ||||
|                 </LegacyEnvironmentHeader> | ||||
|                 <NewStyledAccordionDetails> | ||||
|                     <StyledEnvironmentAccordionContainer> | ||||
|                         <EnvironmentAccordionBody | ||||
|                             featureEnvironment={environment} | ||||
|                             isDisabled={!environment.enabled} | ||||
|                             otherEnvironments={otherEnvironments} | ||||
|                         /> | ||||
|                     </StyledEnvironmentAccordionContainer> | ||||
|                     <StyledAccordionFooter> | ||||
|                         <Box sx={{ display: 'flex', flexDirection: 'row' }}> | ||||
|                             <ReleaseTemplatesFeedback /> | ||||
|                             <Box ml='auto'> | ||||
|                                 <FeatureStrategyMenu | ||||
|                                     label='Add strategy' | ||||
|                                     projectId={projectId} | ||||
|                                     featureId={featureId} | ||||
|                                     environmentId={environment.name} | ||||
|                                 /> | ||||
|                             </Box> | ||||
|                         </Box> | ||||
|                         {isOss() && environment?.type === 'production' ? ( | ||||
|                             <UpgradeChangeRequests /> | ||||
|                         ) : null} | ||||
|                     </StyledAccordionFooter> | ||||
|                 </NewStyledAccordionDetails> | ||||
|             </StyledAccordion> | ||||
|         </StyledFeatureOverviewEnvironment> | ||||
|     ); | ||||
| }; | ||||
| @ -1,10 +1,12 @@ | ||||
| import type { ComponentProps, FC } from 'react'; | ||||
| import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; | ||||
| import { LegacyFeatureOverviewEnvironment } from './FeatureOverviewEnvironment/LegacyFeatureOverviewEnvironment/LegacyFeatureOverviewEnvironment.tsx'; | ||||
| import { FeatureOverviewEnvironment } from './FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics'; | ||||
| import { getFeatureMetrics } from 'utils/getFeatureMetrics'; | ||||
| import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| 
 | ||||
| type FeatureOverviewEnvironmentsProps = { | ||||
|     hiddenEnvironments?: string[]; | ||||
| @ -12,7 +14,7 @@ type FeatureOverviewEnvironmentsProps = { | ||||
| }; | ||||
| 
 | ||||
| const FeatureOverviewWithReleasePlans: FC< | ||||
|     ComponentProps<typeof FeatureOverviewEnvironment> | ||||
|     ComponentProps<typeof LegacyFeatureOverviewEnvironment> | ||||
| > = ({ environment, ...props }) => { | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
|     const featureId = useRequiredPathParam('featureId'); | ||||
| @ -21,9 +23,20 @@ const FeatureOverviewWithReleasePlans: FC< | ||||
|         featureId, | ||||
|         environment?.name, | ||||
|     ); | ||||
|     const envAddStrategySuggestionEnabled = useUiFlag( | ||||
|         'envAddStrategySuggestion', | ||||
|     ); | ||||
|     if (envAddStrategySuggestionEnabled) { | ||||
|         return ( | ||||
|             <FeatureOverviewEnvironment | ||||
|                 {...props} | ||||
|                 environment={{ ...environment, releasePlans }} | ||||
|             /> | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <FeatureOverviewEnvironment | ||||
|         <LegacyFeatureOverviewEnvironment | ||||
|             {...props} | ||||
|             environment={{ ...environment, releasePlans }} | ||||
|         /> | ||||
|  | ||||
| @ -2,7 +2,7 @@ import { Accordion, AccordionDetails, styled } from '@mui/material'; | ||||
| import { PROJECT_ENVIRONMENT_ACCORDION } from 'utils/testIds'; | ||||
| import type { ProjectEnvironmentType } from '../../../../../../interfaces/environments.ts'; | ||||
| import { ProjectEnvironmentDefaultStrategy } from './ProjectEnvironmentDefaultStrategy/ProjectEnvironmentDefaultStrategy.tsx'; | ||||
| import { EnvironmentHeader } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/EnvironmentHeader'; | ||||
| import { LegacyEnvironmentHeader } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/LegacyEnvironmentHeader/LegacyEnvironmentHeader'; | ||||
| 
 | ||||
| interface IProjectEnvironmentProps { | ||||
|     environment: ProjectEnvironmentType; | ||||
| @ -35,7 +35,10 @@ export const ProjectEnvironment = ({ | ||||
|                 onChange={(e) => e.stopPropagation()} | ||||
|                 data-testid={`${PROJECT_ENVIRONMENT_ACCORDION}_${name}`} | ||||
|             > | ||||
|                 <EnvironmentHeader environmentId={name} expandable={false} /> | ||||
|                 <LegacyEnvironmentHeader | ||||
|                     environmentId={name} | ||||
|                     expandable={false} | ||||
|                 /> | ||||
|                 <StyledAccordionDetails> | ||||
|                     <ProjectEnvironmentDefaultStrategy | ||||
|                         environment={environment} | ||||
|  | ||||
| @ -41,6 +41,7 @@ export type CustomEvents = | ||||
|     | 'context-usage' | ||||
|     | 'segment-usage' | ||||
|     | 'strategy-add' | ||||
|     | 'suggestion-strategy-add' | ||||
|     | 'playground' | ||||
|     | 'feature-type-edit' | ||||
|     | 'strategy-variants' | ||||
|  | ||||
| @ -91,6 +91,7 @@ export type UiFlags = { | ||||
|     flagsUiFilterRefactor?: boolean; | ||||
|     trafficBillingDisplay?: boolean; | ||||
|     milestoneProgression?: boolean; | ||||
|     envAddStrategySuggestion?: boolean; | ||||
| }; | ||||
| 
 | ||||
| export interface IVersionInfo { | ||||
|  | ||||
| @ -62,7 +62,8 @@ export type IFlagKey = | ||||
|     | 'newUiConfigService' | ||||
|     | 'flagsUiFilterRefactor' | ||||
|     | 'trafficBillingDisplay' | ||||
|     | 'milestoneProgression'; | ||||
|     | 'milestoneProgression' | ||||
|     | 'envAddStrategySuggestion'; | ||||
| 
 | ||||
| export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; | ||||
| 
 | ||||
| @ -287,6 +288,10 @@ const flags: IFlags = { | ||||
|         process.env.UNLEASH_EXPERIMENTAL_MILESTONE_PROGRESSION, | ||||
|         false, | ||||
|     ), | ||||
|     envAddStrategySuggestion: parseEnvVarBoolean( | ||||
|         process.env.UNLEASH_EXPERIMENTAL_ENV_ADD_STRATEGY_SUGGESTION, | ||||
|         false, | ||||
|     ), | ||||
| }; | ||||
| 
 | ||||
| export const defaultExperimentalOptions: IExperimentalOptions = { | ||||
|  | ||||
| @ -59,6 +59,7 @@ process.nextTick(async () => { | ||||
|                         flagsUiFilterRefactor: true, | ||||
|                         trafficBillingDisplay: true, | ||||
|                         milestoneProgression: true, | ||||
|                         envAddStrategySuggestion: true, | ||||
|                     }, | ||||
|                 }, | ||||
|                 authentication: { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user