mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
refactor: rewrite feature strategy icons list (#1039)
* refactor: fix strategy action icon layout * refactor: use a custom SVG for the rollout strategy icon * refactor: rewrite feature strategy icons list
This commit is contained in:
parent
670bb33fad
commit
f4d02e37b7
1
frontend/src/assets/icons/rollout.svg
Normal file
1
frontend/src/assets/icons/rollout.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M16.584 10H22a2 2 0 1 1 0 4h-5.416a5.001 5.001 0 0 1-9.168 0H2a2 2 0 1 1 0-4h5.416a5.001 5.001 0 0 1 9.168 0Z" /></svg>
|
After Width: | Height: | Size: 272 B |
@ -1,27 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
position: 'relative',
|
|
||||||
width: '50px',
|
|
||||||
height: '100%',
|
|
||||||
padding: '15px 0px',
|
|
||||||
},
|
|
||||||
vertical: {
|
|
||||||
borderRadius: '1px',
|
|
||||||
height: '50px',
|
|
||||||
width: '50px',
|
|
||||||
},
|
|
||||||
circle: {
|
|
||||||
width: '15px',
|
|
||||||
height: '15px',
|
|
||||||
},
|
|
||||||
pos: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: 0,
|
|
||||||
left: 0,
|
|
||||||
margin: '0 auto',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,24 +0,0 @@
|
|||||||
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
|
|
||||||
import Remove from '@mui/icons-material/Remove';
|
|
||||||
import { useStyles } from './RolloutIcon.styles';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
interface IRolloutIconProps {
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RolloutIcon = ({ className }: IRolloutIconProps) => {
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<Remove
|
|
||||||
className={classnames(styles.vertical, styles.pos, className)}
|
|
||||||
/>
|
|
||||||
<FiberManualRecordIcon
|
|
||||||
className={classnames(styles.circle, styles.pos, className)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RolloutIcon;
|
|
@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
getFeatureStrategyIcon,
|
||||||
|
formatStrategyName,
|
||||||
|
} from 'utils/strategyNames';
|
||||||
|
import { styled, Tooltip } from '@mui/material';
|
||||||
|
import { useId } from 'hooks/useId';
|
||||||
|
|
||||||
|
interface IFeatureStrategyIconProps {
|
||||||
|
strategyName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FeatureStrategyIcon = ({
|
||||||
|
strategyName,
|
||||||
|
}: IFeatureStrategyIconProps) => {
|
||||||
|
const Icon = getFeatureStrategyIcon(strategyName);
|
||||||
|
const id = useId();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledIcon>
|
||||||
|
<Tooltip title={formatStrategyName(strategyName)} arrow>
|
||||||
|
<div id={id} role="tooltip">
|
||||||
|
<Icon aria-labelledby={id} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</StyledIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledIcon = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: theme.palette.inactiveIcon,
|
||||||
|
|
||||||
|
'& svg': {
|
||||||
|
width: theme.spacing(2.5),
|
||||||
|
height: theme.spacing(2.5),
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,45 @@
|
|||||||
|
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
import { FeatureStrategyIcon } from 'component/feature/FeatureStrategy/FeatureStrategyIcon/FeatureStrategyIcon';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
|
interface IFeatureStrategyIconsProps {
|
||||||
|
strategies: IFeatureStrategy[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FeatureStrategyIcons = ({
|
||||||
|
strategies,
|
||||||
|
}: IFeatureStrategyIconsProps) => {
|
||||||
|
if (!strategies?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const strategyNames = strategies.map(strategy => strategy.name);
|
||||||
|
const uniqueStrategyNames = uniqueValues(strategyNames);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledList aria-label="Feature strategies">
|
||||||
|
{uniqueStrategyNames.map(strategyName => (
|
||||||
|
<StyledListItem key={strategyName}>
|
||||||
|
<FeatureStrategyIcon strategyName={strategyName} />
|
||||||
|
</StyledListItem>
|
||||||
|
))}
|
||||||
|
</StyledList>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const uniqueValues = <T,>(values: T[]): T[] => {
|
||||||
|
return [...new Set(values)];
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledList = styled('ul')(() => ({
|
||||||
|
all: 'unset',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
alignContent: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledListItem = styled('li')(() => ({
|
||||||
|
all: 'unset',
|
||||||
|
minWidth: 30,
|
||||||
|
textAlign: 'center',
|
||||||
|
}));
|
@ -94,30 +94,11 @@ export const useStyles = makeStyles()(theme => ({
|
|||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
marginBottom: '1rem',
|
marginBottom: '1rem',
|
||||||
},
|
},
|
||||||
strategyIconContainer: {
|
|
||||||
minWidth: '40px',
|
|
||||||
marginRight: '5px',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
strategiesIconsContainer: {
|
|
||||||
transform: 'scale(0.8)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
position: 'relative',
|
|
||||||
[theme.breakpoints.down(560)]: {
|
|
||||||
marginLeft: '0px',
|
|
||||||
top: '5px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
truncator: {
|
truncator: {
|
||||||
[theme.breakpoints.down(560)]: {
|
[theme.breakpoints.down(560)]: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
strategyIcon: {
|
|
||||||
fill: theme.palette.inactiveIcon,
|
|
||||||
},
|
|
||||||
container: {
|
container: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@ -127,7 +108,4 @@ export const useStyles = makeStyles()(theme => ({
|
|||||||
marginLeft: '0',
|
marginLeft: '0',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
strategyMenu: {
|
|
||||||
marginRight: '-.5rem',
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
@ -1,19 +1,10 @@
|
|||||||
import {
|
import { Accordion, AccordionDetails, AccordionSummary } from '@mui/material';
|
||||||
Accordion,
|
|
||||||
AccordionDetails,
|
|
||||||
AccordionSummary,
|
|
||||||
Tooltip,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { ExpandMore } from '@mui/icons-material';
|
import { ExpandMore } from '@mui/icons-material';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
|
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
|
||||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
|
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
|
||||||
import {
|
|
||||||
getFeatureStrategyIcon,
|
|
||||||
formatStrategyName,
|
|
||||||
} from 'utils/strategyNames';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
@ -25,12 +16,7 @@ import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureSt
|
|||||||
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { StatusBadge } from 'component/common/StatusBadge/StatusBadge';
|
import { StatusBadge } from 'component/common/StatusBadge/StatusBadge';
|
||||||
|
import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons';
|
||||||
interface IStrategyIconObject {
|
|
||||||
count: number;
|
|
||||||
Icon: React.ReactElement;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IFeatureOverviewEnvironmentProps {
|
interface IFeatureOverviewEnvironmentProps {
|
||||||
env: IFeatureEnvironment;
|
env: IFeatureEnvironment;
|
||||||
@ -55,36 +41,11 @@ const FeatureOverviewEnvironment = ({
|
|||||||
|
|
||||||
const getOverviewText = () => {
|
const getOverviewText = () => {
|
||||||
if (env.enabled) {
|
if (env.enabled) {
|
||||||
return `${environmentMetric?.yes} received this feature
|
return `${environmentMetric?.yes} received this feature because the following strategies are executing`;
|
||||||
because the following strategies are executing`;
|
|
||||||
}
|
}
|
||||||
return `This environment is disabled, which means that none of your strategies are executing`;
|
return `This environment is disabled, which means that none of your strategies are executing`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStrategyIcons = () => {
|
|
||||||
const strategyObjects = featureEnvironment?.strategies.reduce(
|
|
||||||
(acc, current) => {
|
|
||||||
if (acc[current.name]) {
|
|
||||||
acc[current.name].count = acc[current.name].count + 1;
|
|
||||||
} else {
|
|
||||||
acc[current.name] = {
|
|
||||||
count: 1,
|
|
||||||
// @ts-expect-error
|
|
||||||
Icon: getFeatureStrategyIcon(current.name),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as { [key: string]: IStrategyIconObject }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!strategyObjects) return [];
|
|
||||||
|
|
||||||
return Object.keys(strategyObjects).map(strategyName => {
|
|
||||||
return { ...strategyObjects[strategyName], name: strategyName };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.featureOverviewEnvironment}>
|
<div className={styles.featureOverviewEnvironment}>
|
||||||
<Accordion
|
<Accordion
|
||||||
@ -112,7 +73,6 @@ const FeatureOverviewEnvironment = ({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.strategyMenu}>
|
|
||||||
<FeatureStrategyMenu
|
<FeatureStrategyMenu
|
||||||
label="Add strategy"
|
label="Add strategy"
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
@ -120,45 +80,8 @@ const FeatureOverviewEnvironment = ({
|
|||||||
environmentId={env.name}
|
environmentId={env.name}
|
||||||
variant="text"
|
variant="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
<FeatureStrategyIcons
|
||||||
<ConditionallyRender
|
strategies={featureEnvironment?.strategies}
|
||||||
condition={
|
|
||||||
featureEnvironment?.strategies.length !== 0
|
|
||||||
}
|
|
||||||
show={
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
styles.strategiesIconsContainer
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{getStrategyIcons()?.map(
|
|
||||||
({ name, Icon }) => (
|
|
||||||
<Tooltip
|
|
||||||
title={formatStrategyName(
|
|
||||||
name
|
|
||||||
)}
|
|
||||||
arrow
|
|
||||||
key={name}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
styles.strategyIconContainer
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{/* @ts-expect-error */}
|
|
||||||
<Icon
|
|
||||||
className={
|
|
||||||
styles.strategyIcon
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>{' '}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
|
@ -20,6 +20,7 @@ export const useStyles = makeStyles()(theme => ({
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
|
display: 'flex',
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
padding: '1rem',
|
padding: '1rem',
|
||||||
|
@ -2,7 +2,7 @@ import LocationOnIcon from '@mui/icons-material/LocationOn';
|
|||||||
import PeopleIcon from '@mui/icons-material/People';
|
import PeopleIcon from '@mui/icons-material/People';
|
||||||
import LanguageIcon from '@mui/icons-material/Language';
|
import LanguageIcon from '@mui/icons-material/Language';
|
||||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||||
import RolloutIcon from 'component/common/RolloutIcon/RolloutIcon';
|
import { ReactComponent as RolloutIcon } from 'assets/icons/rollout.svg';
|
||||||
import { ElementType } from 'react';
|
import { ElementType } from 'react';
|
||||||
|
|
||||||
export const formatStrategyName = (strategyName: string): string => {
|
export const formatStrategyName = (strategyName: string): string => {
|
||||||
|
Loading…
Reference in New Issue
Block a user