Skip to content

Feat(UI): add ability to collapse/expand strategies #9548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ type StrategyItemProps = {
type?: ReactNode;
children?: ReactNode;
values?: string[];
reduceMargin?: boolean;
};

const StyledContainer = styled('div')(({ theme }) => ({
const StyledContainer = styled('div', {
shouldForwardProp: prop => prop !== 'reduceMargin',
})<{ reduceMargin: boolean }>(({ theme, reduceMargin }) => ({
display: 'flex',
gap: theme.spacing(1),
alignItems: 'center',
fontSize: theme.typography.body2.fontSize,
margin: theme.spacing(2, 3),
margin: reduceMargin ? 0 : theme.spacing(2, 3),
}));

const StyledContent = styled('div')(({ theme }) => ({
Expand Down Expand Up @@ -51,8 +54,9 @@ export const StrategyEvaluationItem: FC<StrategyItemProps> = ({
type,
children,
values,
reduceMargin = false,
}) => (
<StyledContainer>
<StyledContainer reduceMargin={reduceMargin}>
<StyledType>{type}</StyledType>
<StyledContent>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type React from 'react';
import type { DragEventHandler, FC, ReactNode } from 'react';
import DragIndicator from '@mui/icons-material/DragIndicator';
import { Box, IconButton, Typography, styled } from '@mui/material';
import { Box, Chip, IconButton, Typography, styled } from '@mui/material';
import type { IFeatureStrategy } from 'interfaces/strategy';
import { formatStrategyName } from 'utils/strategyNames';
import type { PlaygroundStrategySchema } from 'openapi';
import { Badge } from '../Badge/Badge';
import { Link } from 'react-router-dom';
import { Truncator } from '../Truncator/Truncator';
import { StrategyEvaluationChip } from '../ConstraintsList/StrategyEvaluationChip/StrategyEvaluationChip';
import { RolloutVariants } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/RolloutParameter/RolloutVariants/RolloutVariants';

type StrategyItemContainerProps = {
strategyHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
Expand All @@ -19,6 +21,7 @@ type StrategyItemContainerProps = {
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
isCollapsed?: boolean;
};

const inlinePadding = 3;
Expand All @@ -41,10 +44,13 @@ const StyledHeaderContainer = styled('hgroup')(({ theme }) => ({
},
}));

const StyledContainer = styled('article')(({ theme }) => ({
const StyledContainer = styled('article', {
shouldForwardProp: prop => prop !== 'collapsed',
})<{ collapsed: boolean }>(({ theme, collapsed }) => ({
background: 'inherit',
padding: theme.spacing(inlinePadding),
paddingTop: theme.spacing(0.5),
paddingBottom: collapsed ? theme.spacing(0.5) : undefined,
display: 'flex',
flexDirection: 'column',
rowGap: theme.spacing(0.5),
Expand Down Expand Up @@ -80,15 +86,16 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
children,
style = {},
className,
isCollapsed = false,
}) => {
const StrategyHeaderLink: React.FC<{ children?: React.ReactNode }> =
'links' in strategy
? ({ children }) => <Link to={strategy.links.edit}>{children}</Link>
: ({ children }) => <> {children} </>;

return (
<Box sx={{ position: 'relative' }}>
<StyledContainer style={style} className={className}>
<StyledContainer style={style} className={className} collapsed={isCollapsed}>
<StyledHeader disabled={Boolean(strategy?.disabled)}>
{onDragStart ? (
<DragIcon
Expand Down Expand Up @@ -139,6 +146,9 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
{strategy.disabled ? (
<Badge color='disabled'>Disabled</Badge>
) : null}
{isCollapsed && strategy.parameters.rollout ? (
<StrategyEvaluationChip label={`${strategy.parameters.rollout}% Rollout`} />
) : null}
{headerItemsLeft}
</StyledHeaderInner>
<Box
Expand All @@ -151,6 +161,9 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
{headerItemsRight}
</Box>
</StyledHeader>
{isCollapsed && 'variants' in strategy ? (
<RolloutVariants variants={strategy.variants} reduceMargin />
) : null}
<Box>{children}</Box>
</StyledContainer>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useUiFlag } from 'hooks/useUiFlag';
import { FeatureOverviewEnvironments } from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
import { default as LegacyFleatureOverview } from './LegacyFeatureOverview';
import { useEnvironmentVisibility } from './FeatureOverviewMetaData/EnvironmentVisibilityMenu/hooks/useEnvironmentVisibility';
import { CollapsedStrategiesProvider } from './FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/CollapseStrategyIcon/hooks/useCollapsedStrategies';

const StyledContainer = styled('div')(({ theme }) => ({
display: 'flex',
Expand Down Expand Up @@ -62,9 +63,11 @@ export const FeatureOverview = () => {
/>
</div>
<StyledMainContent>
<FeatureOverviewEnvironments
hiddenEnvironments={hiddenEnvironments}
/>
<CollapsedStrategiesProvider>
<FeatureOverviewEnvironments
hiddenEnvironments={hiddenEnvironments}
/>
</CollapsedStrategiesProvider>
</StyledMainContent>
<Routes>
<Route
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export const EnvironmentAccordionBody = ({
{strategies.length > 0 ? (
<li>
<StrategySeparator />
{strategyList}
{strategyList}
</li>
) : null}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import MenuStrategyRemove from './StrategyItem/MenuStrategyRemove/MenuStrategyRe
import { Link } from 'react-router-dom';
import { UPDATE_FEATURE_STRATEGY } from '@server/types/permissions';
import { StrategyDraggableItem } from './StrategyDraggableItem';
import { CollapseStrategyIcon } from './StrategyItem/CollapseStrategyIcon';

type ProjectEnvironmentStrategyDraggableItemProps = {
strategy: IFeatureStrategy;
Expand Down Expand Up @@ -104,6 +105,7 @@ export const ProjectEnvironmentStrategyDraggableItem = ({
).map((scheduledChange) => scheduledChange.id)}
/>
) : null}
<CollapseStrategyIcon strategy={strategy} />
{otherEnvironments && otherEnvironments?.length > 0 ? (
<CopyStrategyIconMenu
environmentId={environmentName}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { IconButton, Tooltip } from '@mui/material';
import { useCollapsedStrategies } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/CollapseStrategyIcon/hooks/useCollapsedStrategies';
import type { IFeatureStrategyPayload } from 'interfaces/strategy';
import { type VFC } from 'react';
import { STRATEGY_FORM_COPY_ID } from 'utils/testIds';
import { useIsCollapsed } from './hooks/useIsCollapsed';

interface ICollapseStrategyIconProps {
strategy: IFeatureStrategyPayload;
}

export const CollapseStrategyIcon: VFC<ICollapseStrategyIconProps> = ({
strategy,
}) => {
const { collapseStrategy, expandStrategy } = useCollapsedStrategies();

const collapsed = useIsCollapsed(strategy.id);

const label = collapsed ? 'Expand Strategy' : 'Collapse Strategy';

const onClick = () => {
if (!strategy.id) return
if (collapsed) {
expandStrategy(strategy.id);
} else {
collapseStrategy(strategy.id);
}
}

if (!strategy.id) {
return null;
}

return (
<div>
<Tooltip title={label}>
<div>
<IconButton
size='large'
id={`copy-strategy-icon-menu-${strategy.id}`}
aria-label={label}
aria-haspopup='true'
aria-expanded={collapsed ? undefined : 'true'}
onClick={onClick}
data-testid={STRATEGY_FORM_COPY_ID}
>
{collapsed
? <ExpandMoreIcon/>
: <ExpandLessIcon />
}
</IconButton>
</div>
</Tooltip>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { createContext, useCallback, useContext, useState } from 'react';

interface ICollapsedStrategiesContextProps {
collapsedStrategies: Set<string>;
collapseStrategy: (strategyId: string) => void;
expandStrategy: (strategyId: string) => void;
}

const CollapsedStrategiesContext = createContext({} as ICollapsedStrategiesContextProps);

export const useCollapsedStrategies = () => {
return useContext(CollapsedStrategiesContext);
}

export const CollapsedStrategiesProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
const [collapsedStrategies, setCollapsedStrategies] = useState(new Set<string>());

const collapseStrategy = useCallback((strategyId: string) => {
setCollapsedStrategies(prev => new Set(prev).add(strategyId));
}, []);

const expandStrategy = useCallback((strategyId: string) => {
setCollapsedStrategies(prev => {
const newSet = new Set(prev);
newSet.delete(strategyId);
return newSet;
});
}, []);

const value = {
collapsedStrategies,
collapseStrategy,
expandStrategy,
};

return (
<CollapsedStrategiesContext.Provider value={value}>
{children}
</CollapsedStrategiesContext.Provider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useCollapsedStrategies } from './useCollapsedStrategies';

export const useIsCollapsed = (strategyId: string | undefined) => {
const { collapsedStrategies } = useCollapsedStrategies();

if (!strategyId) return false;

return collapsedStrategies.has(strategyId);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './CollapseStrageyIcon';
export * from './hooks/useIsCollapsed';

Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ const StyledPayloadHeader = styled('div')(({ theme }) => ({

export const RolloutVariants: FC<{
variants?: StrategyVariantSchema[];
}> = ({ variants }) => {
reduceMargin?: boolean;
}> = ({ variants, reduceMargin }) => {
if (!variants?.length) {
return null;
}

return (
<StrategyEvaluationItem type={`Variants (${variants.length})`}>
<StrategyEvaluationItem type={`Variants (${variants.length})`} reduceMargin={reduceMargin}>
{variants.map((variant, i) => (
<HtmlTooltip
arrow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import type { DragEventHandler, FC, ReactNode } from 'react';
import type { IFeatureStrategy } from 'interfaces/strategy';
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
import { useIsCollapsed } from './CollapseStrategyIcon';

type StrategyItemProps = {
headerItemsRight?: ReactNode;
strategy: Omit<IFeatureStrategy, 'id'>;
strategy: IFeatureStrategy;
onDragStart?: DragEventHandler<HTMLButtonElement>;
onDragEnd?: DragEventHandler<HTMLButtonElement>;
strategyHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
Expand All @@ -18,15 +19,18 @@ export const StrategyItem: FC<StrategyItemProps> = ({
headerItemsRight,
strategyHeaderLevel,
}) => {
const isCollapsed = useIsCollapsed(strategy.id);

return (
<StrategyItemContainer
strategyHeaderLevel={strategyHeaderLevel}
strategy={strategy}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
headerItemsRight={headerItemsRight}
isCollapsed={isCollapsed}
>
<StrategyExecution strategy={strategy} />
{!isCollapsed && <StrategyExecution strategy={strategy} />}
</StrategyItemContainer>
);
};
Loading