diff --git a/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.tsx b/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.tsx index 7be8459bf5..d1018c2d03 100644 --- a/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.tsx +++ b/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.tsx @@ -4,7 +4,6 @@ import DragIndicator from '@mui/icons-material/DragIndicator'; import { Box, IconButton, Typography, styled } from '@mui/material'; import type { IFeatureStrategy } from 'interfaces/strategy'; import { formatStrategyName } from 'utils/strategyNames'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import type { PlaygroundStrategySchema } from 'openapi'; import { Badge } from '../Badge/Badge'; import { Link } from 'react-router-dom'; @@ -16,16 +15,20 @@ type StrategyItemContainerProps = { onDragStart?: DragEventHandler; onDragEnd?: DragEventHandler; headerItemsRight?: ReactNode; + headerItemsLeft?: ReactNode; className?: string; style?: React.CSSProperties; children?: React.ReactNode; }; -const DragIcon = styled(IconButton)({ +const inlinePadding = 3; + +const DragIcon = styled(IconButton)(({ theme }) => ({ + marginLeft: theme.spacing(-inlinePadding), padding: 0, cursor: 'inherit', transition: 'color 0.2s ease-in-out', -}); +})); const StyledHeaderContainer = styled('hgroup')(({ theme }) => ({ display: 'flex', @@ -38,9 +41,14 @@ const StyledHeaderContainer = styled('hgroup')(({ theme }) => ({ }, })); -const StyledContainer = styled('article')({ +const StyledContainer = styled('article')(({ theme }) => ({ background: 'inherit', -}); + padding: theme.spacing(inlinePadding), + paddingTop: theme.spacing(0.5), + display: 'flex', + flexDirection: 'column', + rowGap: theme.spacing(0.5), +})); const StyledTruncator = styled(Truncator)(({ theme }) => ({ fontSize: theme.typography.body1.fontSize, @@ -49,102 +57,101 @@ const StyledTruncator = styled(Truncator)(({ theme }) => ({ })); const StyledHeader = styled('div', { - shouldForwardProp: (prop) => prop !== 'draggable' && prop !== 'disabled', -})<{ draggable: boolean; disabled: boolean }>( - ({ theme, draggable, disabled }) => ({ - padding: theme.spacing(0.5, 2), - display: 'flex', - gap: theme.spacing(1), - alignItems: 'center', - paddingLeft: draggable ? theme.spacing(1) : theme.spacing(2), - color: disabled - ? theme.palette.text.secondary - : theme.palette.text.primary, - }), -); + shouldForwardProp: (prop) => prop !== 'disabled', +})<{ disabled: boolean }>(({ theme, disabled }) => ({ + display: 'flex', + alignItems: 'center', + color: disabled ? theme.palette.text.secondary : theme.palette.text.primary, +})); + +const StyledHeaderInner = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), +})); export const StrategyItemContainer: FC = ({ strategy, onDragStart, onDragEnd, headerItemsRight, + headerItemsLeft, strategyHeaderLevel = 3, children, style = {}, + className, }) => { const StrategyHeaderLink: React.FC<{ children?: React.ReactNode }> = - 'links' in strategy // todo: revisit this when we get to playground, related to flag `flagOverviewRedesign` + 'links' in strategy ? ({ children }) => {children} : ({ children }) => <> {children} ; return ( - - - ( - - - - )} - /> - - - {strategy.title ? ( - <> -

+ + + {onDragStart ? ( + + + + ) : null} + + + + {strategy.title ? ( + <> +

+ {formatStrategyName( + String(strategy.name), + )} + : +

+ + {strategy.title} + + + ) : ( + {formatStrategyName( String(strategy.name), )} - : -

- - {strategy.title} - - - ) : ( - - {formatStrategyName(String(strategy.name))} - - )} -
-
+ + )} + + - {strategy.disabled ? ( - Disabled - ) : null} + {strategy.disabled ? ( + Disabled + ) : null} + {headerItemsLeft} + theme.spacing(6), alignItems: 'center', }} > {headerItemsRight}
- {children} + {children}
); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx index abb24d5b36..c09eb9e79e 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx @@ -30,9 +30,7 @@ interface IEnvironmentAccordionBodyProps { } const StyledAccordionBodyInnerContainer = styled('div')(({ theme }) => ({ - [theme.breakpoints.down(400)]: { - padding: theme.spacing(1), - }, + borderBottom: `1px solid ${theme.palette.divider}`, })); export const StyledContentList = styled('ol')(({ theme }) => ({ @@ -44,6 +42,9 @@ export const StyledContentList = styled('ol')(({ theme }) => ({ paddingBlock: theme.spacing(2.5), position: 'relative', }, + '& > li + li': { + borderTop: `1px solid ${theme.palette.divider}`, + }, '&:not(li > &) > li:first-of-type': { // select first list elements in lists that are not directly nested // within other lists. @@ -58,7 +59,6 @@ export const StyledContentList = styled('ol')(({ theme }) => ({ export const StyledListItem = styled('li', { shouldForwardProp: (prop) => prop !== 'type', })<{ type?: 'release plan' | 'strategy' }>(({ theme, type }) => ({ - borderBottom: `1px solid ${theme.palette.divider}`, background: type === 'release plan' ? theme.palette.background.elevation2 @@ -290,34 +290,29 @@ export const EnvironmentAccordionBody = ({ }; return ( -
- - - {releasePlans.length > 0 ? ( - <> - {releasePlans.map((plan) => ( - - - - ))} - {strategies.length > 0 ? ( -
  • - - -
  • - ) : null} - - ) : strategies.length > 0 ? ( - - ) : null} -
    -
    -
    + + + {releasePlans.length > 0 ? ( + <> + {releasePlans.map((plan) => ( + + + + ))} + {strategies.length > 0 ? ( +
  • + + +
  • + ) : null} + + ) : strategies.length > 0 ? ( + + ) : null} +
    +
    ); }; diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureResultInfoPopoverCell.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureResultInfoPopoverCell.tsx index dc5b418e9f..fe8e82c7a9 100644 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureResultInfoPopoverCell.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureResultInfoPopoverCell.tsx @@ -18,22 +18,17 @@ const FeatureResultPopoverWrapper = styled('div')(({ theme }) => ({ color: theme.palette.divider, })); -export const FeatureResultInfoPopoverCell = ({ +const LegacyFeatureResultInfoPopoverCell = ({ feature, input, }: FeatureResultInfoPopoverCellProps) => { const [open, setOpen] = useState(false); const ref = useRef(null); - const useNewStrategyDesign = useUiFlag('flagOverviewRedesign'); const togglePopover = () => { setOpen(!open); }; - if (!feature) { - return null; - } - return ( @@ -47,7 +42,7 @@ export const FeatureResultInfoPopoverCell = ({ sx: (theme) => ({ display: 'flex', flexDirection: 'column', - padding: theme.spacing(useNewStrategyDesign ? 4 : 6), + padding: theme.spacing(6), width: 728, maxWidth: '100%', height: 'auto', @@ -65,32 +60,96 @@ export const FeatureResultInfoPopoverCell = ({ horizontal: 'left', }} > - {useNewStrategyDesign ? ( - <> - setOpen(false)} - /> - - - ) : ( - <> - setOpen(false)} - /> - - - )} + setOpen(false)} + /> + ); }; + +const DetailsPadding = styled('div')(({ theme }) => ({ + paddingInline: `var(--popover-inline-padding, ${theme.spacing(4)})`, + paddingTop: theme.spacing(2.5), +})); + +export const NewFeatureResultInfoPopoverCell = ({ + feature, + input, +}: FeatureResultInfoPopoverCellProps) => { + const [open, setOpen] = useState(false); + const ref = useRef(null); + + const togglePopover = () => { + setOpen(!open); + }; + + return ( + + + + + setOpen(false)} + anchorEl={ref.current} + PaperProps={{ + sx: (theme) => ({ + '--popover-inline-padding': theme.spacing(4), + display: 'flex', + flexDirection: 'column', + width: 728, + maxWidth: '100%', + height: 'auto', + gap: theme.spacing(3), + overflowY: 'auto', + backgroundColor: theme.palette.background.elevation1, + borderRadius: theme.shape.borderRadius, + }), + }} + anchorOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + transformOrigin={{ + vertical: 'center', + horizontal: 'left', + }} + > + + setOpen(false)} + /> + + + + + ); +}; + +export const FeatureResultInfoPopoverCell = ( + props: FeatureResultInfoPopoverCellProps, +) => { + const useNewStrategyDesign = useUiFlag('flagOverviewRedesign'); + + if (!props.feature) { + return null; + } + + return useNewStrategyDesign ? ( + + ) : ( + + ); +}; diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/LegacyPlaygroundResultFeatureStrategyList.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/LegacyPlaygroundResultFeatureStrategyList.tsx index 396e7881ad..451856d8ef 100644 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/LegacyPlaygroundResultFeatureStrategyList.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/LegacyPlaygroundResultFeatureStrategyList.tsx @@ -1,7 +1,7 @@ import { PlaygroundResultStrategyLists, WrappedPlaygroundResultStrategyList, -} from './StrategyList/playgroundResultStrategyLists'; +} from './StrategyList/LegacyPlaygroundResultStrategyLists'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import type { PlaygroundFeatureSchema, PlaygroundRequestSchema } from 'openapi'; import { Alert } from '@mui/material'; diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultFeatureStrategyList.test.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultFeatureStrategyList.test.tsx index b64be1ee17..0d4b27944d 100644 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultFeatureStrategyList.test.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultFeatureStrategyList.test.tsx @@ -24,7 +24,7 @@ const testCases = [ hasUnsatisfiedDependency: true, } as PlaygroundFeatureSchema, expectedText: - 'If environment was enabled and parent dependencies were satisfied, then this feature flag would be TRUE with strategies evaluated like so:', + 'If the environment was enabled and parent dependencies were satisfied, then this feature flag would be TRUE with strategies evaluated like this:', }, { name: 'Environment enabled and parent dependency not satisfied', @@ -44,7 +44,7 @@ const testCases = [ hasUnsatisfiedDependency: true, } as PlaygroundFeatureSchema, expectedText: - 'If parent dependencies were satisfied, then this feature flag would be TRUE with strategies evaluated like so:', + 'If parent dependencies were satisfied, then this feature flag would be TRUE with strategies evaluated like this:', }, { name: 'Environment not enabled and parent dependency satisfied', @@ -64,7 +64,7 @@ const testCases = [ hasUnsatisfiedDependency: false, } as PlaygroundFeatureSchema, expectedText: - 'If environment was enabled, then this feature flag would be TRUE with strategies evaluated like so:', + 'If the environment was enabled, then this feature flag would be TRUE with strategies evaluated like this:', }, { name: 'Has disabled strategies and is enabled in environment', diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultsFeatureStrategyList.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultsFeatureStrategyList.tsx index 2a3e1b25b3..8a88c62863 100644 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultsFeatureStrategyList.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultsFeatureStrategyList.tsx @@ -50,7 +50,7 @@ export const PlaygroundResultFeatureStrategyList = ({ {showDisabledStrategies ? ( ({ + display: 'flex', + padding: `0, 4px`, + flexDirection: 'column', + borderRadius: theme.shape.borderRadiusMedium, + border: `1px solid ${theme.palette.warning.border}`, +})); + +const StyledListWrapper = styled('div')(({ theme }) => ({ + padding: theme.spacing(1, 0.5), +})); + +const StyledAlert = styled(Alert)(({ theme }) => ({ + border: '0!important', + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + borderBottom: `1px solid ${theme.palette.warning.border}!important`, +})); + +interface PlaygroundResultStrategyListProps { + strategies: PlaygroundStrategySchema[]; + input?: PlaygroundRequestSchema; + titlePrefix?: string; + infoText?: string; +} + +const StyledSubtitle = styled(Typography)(({ theme }) => ({ + margin: theme.spacing(2, 1, 2, 0), + color: 'text.secondary', +})); + +export const PlaygroundResultStrategyLists = ({ + strategies, + input, + titlePrefix, + infoText, +}: PlaygroundResultStrategyListProps) => ( + 0} + show={ + <> + {`${ + titlePrefix + ? titlePrefix.concat(' strategies') + : 'Strategies' + } (${strategies?.length})`} + + {infoText} + + } + /> + + {strategies?.map((strategy, index) => ( + + 0} + show={} + /> + + + ))} + + + } + /> +); + +interface IWrappedPlaygroundResultStrategyListProps { + feature: PlaygroundFeatureSchema; + input?: PlaygroundRequestSchema; +} + +const resolveHintText = (feature: PlaygroundFeatureSchema) => { + if ( + feature.hasUnsatisfiedDependency && + !feature.isEnabledInCurrentEnvironment + ) { + return 'If the environment was enabled and parent dependencies were satisfied'; + } + if (feature.hasUnsatisfiedDependency) { + return 'If parent dependencies were satisfied'; + } + if (!feature.isEnabledInCurrentEnvironment) { + return 'If the environment was enabled'; + } + return ''; +}; + +export const WrappedPlaygroundResultStrategyList = ({ + feature, + input, +}: IWrappedPlaygroundResultStrategyListProps) => { + const enabledStrategies = feature.strategies?.data?.filter( + (strategy) => !strategy.disabled, + ); + const disabledStrategies = feature.strategies?.data?.filter( + (strategy) => strategy.disabled, + ); + + const showDisabledStrategies = disabledStrategies?.length > 0; + + return ( + + + {resolveHintText(feature)}, then this feature flag would be{' '} + {feature.strategies?.result ? 'TRUE' : 'FALSE'} with strategies + evaluated like this:{' '} + + + + + + + + } + /> + + ); +}; diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/FeatureStrategyItem.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/FeatureStrategyItem.tsx index 2587097548..39dd3be10d 100644 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/FeatureStrategyItem.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/FeatureStrategyItem.tsx @@ -8,18 +8,18 @@ import { StrategyExecution } from './StrategyExecution/StrategyExecution'; import { objectId } from 'utils/objectId'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { DisabledStrategyExecution } from './StrategyExecution/DisabledStrategyExecution'; -import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer'; +import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer'; interface IFeatureStrategyItemProps { strategy: PlaygroundStrategySchema; - index: number; input?: PlaygroundRequestSchema; + className?: string; } export const FeatureStrategyItem = ({ strategy, input, - index, + className, }: IFeatureStrategyItemProps) => { const { result } = strategy; const theme = useTheme(); @@ -33,22 +33,19 @@ export const FeatureStrategyItem = ({ return ( } > + {/* todo: use new strategy execution components */} { + const { result } = strategy; + const theme = useTheme(); + const label = + result.evaluationStatus === 'incomplete' || + result.evaluationStatus === 'unevaluated' + ? 'Unevaluated' + : result.enabled + ? 'True' + : 'False'; + + return ( + + } + > + + } + elseShow={ + + } + /> + + ); +}; diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/playgroundResultStrategyLists.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/playgroundResultStrategyLists.tsx index 82d6faa32e..d56d8af0ab 100644 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/playgroundResultStrategyLists.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/playgroundResultStrategyLists.tsx @@ -1,13 +1,16 @@ -import { Fragment } from 'react'; -import { Alert, Box, styled, Typography } from '@mui/material'; +import { Alert, styled } from '@mui/material'; import type { PlaygroundStrategySchema, PlaygroundRequestSchema, PlaygroundFeatureSchema, } from 'openapi'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { + StyledContentList, + StyledListItem, +} from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody'; +import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator'; import { FeatureStrategyItem } from './StrategyItem/FeatureStrategyItem'; -import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator'; const StyledAlertWrapper = styled('div')(({ theme }) => ({ display: 'flex', @@ -31,13 +34,28 @@ const StyledAlert = styled(Alert)(({ theme }) => ({ interface PlaygroundResultStrategyListProps { strategies: PlaygroundStrategySchema[]; input?: PlaygroundRequestSchema; - titlePrefix?: string; + titlePrefix?: 'Enabled' | 'Disabled'; infoText?: string; } +const StyledHeaderGroup = styled('hgroup')(({ theme }) => ({ + paddingInline: `var(--popover-inline-padding, ${theme.spacing(4)})`, + paddingBottom: theme.spacing(2), + borderBottom: `1px solid ${theme.palette.divider}`, +})); -const StyledSubtitle = styled(Typography)(({ theme }) => ({ - margin: theme.spacing(2, 1, 2, 0), - color: 'text.secondary', +const StyledListTitle = styled('h4')(({ theme }) => ({ + fontWeight: 'normal', + fontSize: theme.typography.body1.fontSize, + margin: 0, +})); + +const StyledListTitleDescription = styled('p')(({ theme }) => ({ + fontWeight: 'bold', + fontSize: theme.typography.body2.fontSize, +})); + +const StyledFeatureStrategyItem = styled(FeatureStrategyItem)(({ theme }) => ({ + paddingInline: `var(--popover-inline-padding, ${theme.spacing(4)})`, })); export const PlaygroundResultStrategyLists = ({ @@ -45,44 +63,39 @@ export const PlaygroundResultStrategyLists = ({ input, titlePrefix, infoText, -}: PlaygroundResultStrategyListProps) => ( - 0} - show={ - <> - {`${ +}: PlaygroundResultStrategyListProps) => { + if (strategies.length === 0) { + return null; + } + + return ( +
    + + {`${ titlePrefix ? titlePrefix.concat(' strategies') : 'Strategies' - } (${strategies?.length})`} - - {infoText} - - } - /> - - {strategies?.map((strategy, index) => ( - - 0} - show={} - /> - - - ))} - - - } - /> -); + } (${strategies?.length})`} + {infoText ? ( + + {infoText} + + ) : null} + + + {strategies?.map((strategy, index) => ( + + {index > 0 ? : ''} + + + ))} + +
    + ); +}; interface IWrappedPlaygroundResultStrategyListProps { feature: PlaygroundFeatureSchema; @@ -94,13 +107,13 @@ const resolveHintText = (feature: PlaygroundFeatureSchema) => { feature.hasUnsatisfiedDependency && !feature.isEnabledInCurrentEnvironment ) { - return 'If environment was enabled and parent dependencies were satisfied'; + return 'If the environment was enabled and parent dependencies were satisfied'; } if (feature.hasUnsatisfiedDependency) { return 'If parent dependencies were satisfied'; } if (!feature.isEnabledInCurrentEnvironment) { - return 'If environment was enabled'; + return 'If the environment was enabled'; } return ''; }; @@ -123,19 +136,19 @@ export const WrappedPlaygroundResultStrategyList = ({ {resolveHintText(feature)}, then this feature flag would be{' '} {feature.strategies?.result ? 'TRUE' : 'FALSE'} with strategies - evaluated like so:{' '} + evaluated like this:{' '} - + +