1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00

chore: Playground Strategy Lists (#9510)

Continue the implementation of Playground strategy lists. This PR also
adjusts some existing strategy container and list items to accomodate
more use cases (such as this).

The playground strategy execution component is still the old design.

After (playground results):

![image](https://github.com/user-attachments/assets/f32505ba-f040-4491-a298-6e8bf606536d)

After (env strategy list):

![image](https://github.com/user-attachments/assets/b39174c7-3ee2-4fb4-aa7c-b51134c740b8)

Before (env strategy list):

![image](https://github.com/user-attachments/assets/a0a045e5-3623-44ef-96fa-8ba2f5be6b98)
This commit is contained in:
Thomas Heartman 2025-03-13 12:01:44 +01:00 committed by GitHub
parent 4ddb8fe7d8
commit 732b7f342a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 493 additions and 200 deletions

View File

@ -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<HTMLButtonElement>;
onDragEnd?: DragEventHandler<HTMLButtonElement>;
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<StrategyItemContainerProps> = ({
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 }) => <Link to={strategy.links.edit}>{children}</Link>
: ({ children }) => <> {children} </>;
return (
<Box sx={{ position: 'relative' }}>
<StyledContainer style={style}>
<StyledHeader
draggable={Boolean(onDragStart)}
disabled={Boolean(strategy?.disabled)}
>
<ConditionallyRender
condition={Boolean(onDragStart)}
show={() => (
<DragIcon
draggable
disableRipple
size='small'
onDragStart={onDragStart}
onDragEnd={onDragEnd}
sx={{ cursor: 'move' }}
>
<DragIndicator
titleAccess='Drag to reorder'
cursor='grab'
sx={{ color: 'action.active' }}
/>
</DragIcon>
)}
/>
<StrategyHeaderLink>
<StyledHeaderContainer>
{strategy.title ? (
<>
<p className='strategy-name'>
<StyledContainer style={style} className={className}>
<StyledHeader disabled={Boolean(strategy?.disabled)}>
{onDragStart ? (
<DragIcon
draggable
disableRipple
size='small'
onDragStart={onDragStart}
onDragEnd={onDragEnd}
sx={{ cursor: 'move' }}
>
<DragIndicator
titleAccess='Drag to reorder'
cursor='grab'
sx={{ color: 'action.active' }}
/>
</DragIcon>
) : null}
<StyledHeaderInner>
<StrategyHeaderLink>
<StyledHeaderContainer>
{strategy.title ? (
<>
<p className='strategy-name'>
{formatStrategyName(
String(strategy.name),
)}
:
</p>
<StyledTruncator
component={`h${strategyHeaderLevel}`}
>
{strategy.title}
</StyledTruncator>
</>
) : (
<Typography
className='strategy-name'
component={`h${strategyHeaderLevel}`}
>
{formatStrategyName(
String(strategy.name),
)}
:
</p>
<StyledTruncator
component={`h${strategyHeaderLevel}`}
>
{strategy.title}
</StyledTruncator>
</>
) : (
<Typography
className='strategy-name'
component={`h${strategyHeaderLevel}`}
>
{formatStrategyName(String(strategy.name))}
</Typography>
)}
</StyledHeaderContainer>
</StrategyHeaderLink>
</Typography>
)}
</StyledHeaderContainer>
</StrategyHeaderLink>
{strategy.disabled ? (
<Badge color='disabled'>Disabled</Badge>
) : null}
{strategy.disabled ? (
<Badge color='disabled'>Disabled</Badge>
) : null}
{headerItemsLeft}
</StyledHeaderInner>
<Box
sx={{
marginLeft: 'auto',
display: 'flex',
minHeight: (theme) => theme.spacing(6),
alignItems: 'center',
}}
>
{headerItemsRight}
</Box>
</StyledHeader>
<Box sx={{ p: 2, pt: 0 }}>{children}</Box>
<Box>{children}</Box>
</StyledContainer>
</Box>
);

View File

@ -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 (
<div>
<StyledAccordionBodyInnerContainer>
<StyledContentList>
{releasePlans.length > 0 ? (
<>
{releasePlans.map((plan) => (
<StyledListItem
type='release plan'
key={plan.id}
>
<ReleasePlan
plan={plan}
environmentIsDisabled={isDisabled}
/>
</StyledListItem>
))}
{strategies.length > 0 ? (
<li>
<StrategySeparator />
<Strategies />
</li>
) : null}
</>
) : strategies.length > 0 ? (
<Strategies />
) : null}
</StyledContentList>
</StyledAccordionBodyInnerContainer>
</div>
<StyledAccordionBodyInnerContainer>
<StyledContentList>
{releasePlans.length > 0 ? (
<>
{releasePlans.map((plan) => (
<StyledListItem type='release plan' key={plan.id}>
<ReleasePlan
plan={plan}
environmentIsDisabled={isDisabled}
/>
</StyledListItem>
))}
{strategies.length > 0 ? (
<li>
<StrategySeparator />
<Strategies />
</li>
) : null}
</>
) : strategies.length > 0 ? (
<Strategies />
) : null}
</StyledContentList>
</StyledAccordionBodyInnerContainer>
);
};

View File

@ -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 (
<FeatureResultPopoverWrapper>
<IconButton onClick={togglePopover}>
@ -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 ? (
<>
<FeatureDetails
feature={feature}
input={input}
onClose={() => setOpen(false)}
/>
<PlaygroundResultFeatureStrategyList
feature={feature}
input={input}
/>
</>
) : (
<>
<LegacyFeatureDetails
feature={feature}
input={input}
onClose={() => setOpen(false)}
/>
<LegacyPlaygroundResultFeatureStrategyList
feature={feature}
input={input}
/>
</>
)}
<LegacyFeatureDetails
feature={feature}
input={input}
onClose={() => setOpen(false)}
/>
<LegacyPlaygroundResultFeatureStrategyList
feature={feature}
input={input}
/>
</Popover>
</FeatureResultPopoverWrapper>
);
};
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 (
<FeatureResultPopoverWrapper>
<IconButton onClick={togglePopover}>
<InfoOutlined ref={ref} />
</IconButton>
<Popover
open={open}
onClose={() => 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',
}}
>
<DetailsPadding>
<FeatureDetails
feature={feature}
input={input}
onClose={() => setOpen(false)}
/>
</DetailsPadding>
<PlaygroundResultFeatureStrategyList
feature={feature}
input={input}
/>
</Popover>
</FeatureResultPopoverWrapper>
);
};
export const FeatureResultInfoPopoverCell = (
props: FeatureResultInfoPopoverCellProps,
) => {
const useNewStrategyDesign = useUiFlag('flagOverviewRedesign');
if (!props.feature) {
return null;
}
return useNewStrategyDesign ? (
<NewFeatureResultInfoPopoverCell {...props} />
) : (
<LegacyFeatureResultInfoPopoverCell {...props} />
);
};

View File

@ -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';

View File

@ -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',

View File

@ -50,7 +50,7 @@ export const PlaygroundResultFeatureStrategyList = ({
<PlaygroundResultStrategyLists
strategies={enabledStrategies || []}
input={input}
titlePrefix={showDisabledStrategies ? 'Enabled' : ''}
titlePrefix={showDisabledStrategies ? 'Enabled' : undefined}
/>
{showDisabledStrategies ? (
<PlaygroundResultStrategyLists

View File

@ -0,0 +1,152 @@
import { Fragment } from 'react';
import { Alert, Box, styled, Typography } from '@mui/material';
import type {
PlaygroundStrategySchema,
PlaygroundRequestSchema,
PlaygroundFeatureSchema,
} from 'openapi';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { FeatureStrategyItem } from './StrategyItem/LegacyFeatureStrategyItem';
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
const StyledAlertWrapper = styled('div')(({ theme }) => ({
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) => (
<ConditionallyRender
condition={strategies.length > 0}
show={
<>
<StyledSubtitle variant={'subtitle1'}>{`${
titlePrefix
? titlePrefix.concat(' strategies')
: 'Strategies'
} (${strategies?.length})`}</StyledSubtitle>
<ConditionallyRender
condition={Boolean(infoText)}
show={
<StyledSubtitle variant={'subtitle2'}>
{infoText}
</StyledSubtitle>
}
/>
<Box sx={{ width: '100%' }}>
{strategies?.map((strategy, index) => (
<Fragment key={strategy.id}>
<ConditionallyRender
condition={index > 0}
show={<StrategySeparator text='OR' />}
/>
<FeatureStrategyItem
key={strategy.id}
strategy={strategy}
index={index}
input={input}
/>
</Fragment>
))}
</Box>
</>
}
/>
);
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 (
<StyledAlertWrapper sx={{ pb: 1, mt: 2 }}>
<StyledAlert severity={'info'} color={'warning'}>
{resolveHintText(feature)}, then this feature flag would be{' '}
{feature.strategies?.result ? 'TRUE' : 'FALSE'} with strategies
evaluated like this:{' '}
</StyledAlert>
<StyledListWrapper sx={{ p: 2.5 }}>
<PlaygroundResultStrategyLists
strategies={enabledStrategies || []}
input={input}
titlePrefix={showDisabledStrategies ? 'Enabled' : ''}
/>
</StyledListWrapper>
<ConditionallyRender
condition={showDisabledStrategies}
show={
<StyledListWrapper sx={{ p: 2.5 }}>
<PlaygroundResultStrategyLists
strategies={disabledStrategies}
input={input}
titlePrefix={'Disabled'}
infoText={
'Disabled strategies are not evaluated for the overall result.'
}
/>
</StyledListWrapper>
}
/>
</StyledAlertWrapper>
);
};

View File

@ -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 (
<StrategyItemContainer
style={{
borderColor:
result.enabled && result.evaluationStatus === 'complete'
? theme.palette.success.main
: 'none',
}}
strategy={{ ...strategy, id: `${objectId(strategy)}` }}
orderNumber={index + 1}
actions={
strategyHeaderLevel={4}
className={className}
headerItemsLeft={
<PlaygroundResultChip
tabindex={-1}
showIcon={false}
enabled={result.enabled}
label={label}
/>
}
>
{/* todo: use new strategy execution components */}
<ConditionallyRender
condition={Boolean(strategy.disabled)}
show={

View File

@ -0,0 +1,70 @@
import { useTheme } from '@mui/material';
import { PlaygroundResultChip } from '../../../../PlaygroundResultChip/PlaygroundResultChip';
import type {
PlaygroundStrategySchema,
PlaygroundRequestSchema,
} from 'openapi';
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';
interface IFeatureStrategyItemProps {
strategy: PlaygroundStrategySchema;
index: number;
input?: PlaygroundRequestSchema;
}
export const FeatureStrategyItem = ({
strategy,
input,
index,
}: IFeatureStrategyItemProps) => {
const { result } = strategy;
const theme = useTheme();
const label =
result.evaluationStatus === 'incomplete' ||
result.evaluationStatus === 'unevaluated'
? 'Unevaluated'
: result.enabled
? 'True'
: 'False';
return (
<StrategyItemContainer
style={{
borderColor:
result.enabled && result.evaluationStatus === 'complete'
? theme.palette.success.main
: 'none',
}}
strategy={{ ...strategy, id: `${objectId(strategy)}` }}
orderNumber={index + 1}
actions={
<PlaygroundResultChip
showIcon={false}
enabled={result.enabled}
label={label}
/>
}
>
<ConditionallyRender
condition={Boolean(strategy.disabled)}
show={
<DisabledStrategyExecution
strategyResult={strategy}
input={input}
/>
}
elseShow={
<StrategyExecution
strategyResult={strategy}
input={input}
percentageFill={theme.palette.background.elevation2}
/>
}
/>
</StrategyItemContainer>
);
};

View File

@ -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) => (
<ConditionallyRender
condition={strategies.length > 0}
show={
<>
<StyledSubtitle variant={'subtitle1'}>{`${
}: PlaygroundResultStrategyListProps) => {
if (strategies.length === 0) {
return null;
}
return (
<div>
<StyledHeaderGroup>
<StyledListTitle>{`${
titlePrefix
? titlePrefix.concat(' strategies')
: 'Strategies'
} (${strategies?.length})`}</StyledSubtitle>
<ConditionallyRender
condition={Boolean(infoText)}
show={
<StyledSubtitle variant={'subtitle2'}>
{infoText}
</StyledSubtitle>
}
/>
<Box sx={{ width: '100%' }}>
{strategies?.map((strategy, index) => (
<Fragment key={strategy.id}>
<ConditionallyRender
condition={index > 0}
show={<StrategySeparator text='OR' />}
/>
<FeatureStrategyItem
key={strategy.id}
strategy={strategy}
index={index}
input={input}
/>
</Fragment>
))}
</Box>
</>
}
/>
);
} (${strategies?.length})`}</StyledListTitle>
{infoText ? (
<StyledListTitleDescription>
{infoText}
</StyledListTitleDescription>
) : null}
</StyledHeaderGroup>
<StyledContentList>
{strategies?.map((strategy, index) => (
<StyledListItem key={strategy.id}>
{index > 0 ? <StrategySeparator /> : ''}
<StyledFeatureStrategyItem
strategy={strategy}
input={input}
/>
</StyledListItem>
))}
</StyledContentList>
</div>
);
};
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 = ({
<StyledAlert severity={'info'} color={'warning'}>
{resolveHintText(feature)}, then this feature flag would be{' '}
{feature.strategies?.result ? 'TRUE' : 'FALSE'} with strategies
evaluated like so:{' '}
evaluated like this:{' '}
</StyledAlert>
<StyledListWrapper sx={{ p: 2.5 }}>
<StyledListWrapper>
<PlaygroundResultStrategyLists
strategies={enabledStrategies || []}
input={input}
titlePrefix={showDisabledStrategies ? 'Enabled' : ''}
titlePrefix={showDisabledStrategies ? 'Enabled' : undefined}
/>
</StyledListWrapper>
<ConditionallyRender
condition={showDisabledStrategies}
show={
<StyledListWrapper sx={{ p: 2.5 }}>
<StyledListWrapper>
<PlaygroundResultStrategyLists
strategies={disabledStrategies}
input={input}