1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

fix playground custom strategy parameters debugging (#1213)

* fix playground custom strategy parameters debugging

* fix playground strategy parameters and chips consistency
This commit is contained in:
Tymoteusz Czech 2022-08-11 10:36:23 +02:00 committed by GitHub
parent 2c3b0bbebd
commit d2225c62c9
16 changed files with 245 additions and 195 deletions

View File

@ -7,7 +7,7 @@ import {
StyledToggleButtonOn,
} from '../StyledToggleButton';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { IConstraint } from '../../../../../../interfaces/strategy';
import { IConstraint } from 'interfaces/strategy';
interface CaseSensitiveButtonProps {
localConstraint: IConstraint;

View File

@ -8,8 +8,7 @@ export const useStyles = makeStyles()(theme => ({
},
titleRow: {
display: 'inline-flex',
alignItems: 'flex-start',
justifyContent: 'center',
alignItems: 'center',
gap: theme.spacing(1.5),
marginTop: theme.spacing(1.5),
},

View File

@ -69,12 +69,22 @@ export const FeatureDetails = ({
<Typography variant={'subtitle1'} className={styles.name}>
{feature.name}
</Typography>
<span>
<PlaygroundResultChip
enabled={feature.isEnabled}
label={feature.isEnabled ? 'True' : 'False'}
/>
</span>
<ConditionallyRender
condition={feature?.strategies?.result !== 'unknown'}
show={() => (
<PlaygroundResultChip
enabled={feature.isEnabled}
label={feature.isEnabled ? 'True' : 'False'}
/>
)}
elseShow={() => (
<PlaygroundResultChip
enabled="unknown"
label={'Unknown'}
showIcon={false}
/>
)}
/>
</div>
<IconButton onClick={onCloseClick} className={styles.icon}>
<CloseOutlined />

View File

@ -44,11 +44,6 @@ export const FeatureStrategyItem = ({
showIcon={false}
enabled={result.enabled}
label={label}
size={
result.evaluationStatus === 'incomplete'
? 'large'
: 'default'
}
/>
}
>

View File

@ -1,7 +1,7 @@
import { styled, Tooltip, Typography, useTheme } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PlaygroundSingleValue } from './PlaygroundSingleValue/PlaygroundSingleValue';
import { PLaygroundMultipleValues } from './PlaygroundMultipleValues/PLaygroundMultipleValues';
import { PLaygroundMultipleValues } from './PlaygroundMultipleValues/PlaygroundMultipleValues';
import React from 'react';
import { useStyles } from '../../ConstraintAccordion.styles';
import { CancelOutlined } from '@mui/icons-material';

View File

@ -35,7 +35,7 @@ export const PlaygroundSingleValue = ({
condition={!Boolean(constraint.result)}
show={
<Typography variant={'body1'} color={'error'}>
does not match any values{' '}
does not match values{' '}
</Typography>
}
/>

View File

@ -0,0 +1,97 @@
import { Box, styled, Typography, useTheme } from '@mui/material';
import { CancelOutlined } from '@mui/icons-material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
interface ICustomParameterItem {
text: string;
input?: string | null;
isRequired?: boolean;
}
const StyledWrapper = styled(Box)(({ theme }) => ({
width: '100%',
padding: theme.spacing(2, 3),
borderRadius: theme.shape.borderRadiusMedium,
border: `1px solid ${theme.palette.dividerAlternative}`,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 2,
}));
export const CustomParameterItem = ({
text,
input = null,
isRequired = false,
}: ICustomParameterItem) => {
const theme = useTheme();
const color = input === null ? 'error' : 'neutral';
const requiredError = isRequired && input === null;
return (
<StyledWrapper>
<Typography
variant="subtitle1"
color={theme.palette[color].main}
sx={{ minWidth: 118 }}
>
{`${input === null ? 'no value' : input}`}
</Typography>
<Box
sx={{
flexGrow: 1,
flexDirection: 'column',
}}
>
<Box sx={{ flexGrow: 1 }}>
<ConditionallyRender
condition={Boolean(requiredError)}
show={
<>
<Typography
component="span"
color={theme.palette.error.main}
>
{' required parameter '}
</Typography>
<StringTruncator
maxWidth="300"
text={text}
maxLength={50}
/>
<Typography
component="span"
color={theme.palette.error.main}
>
{' is not set '}
</Typography>
</>
}
elseShow={
<>
<Typography
component="span"
color="text.disabled"
>
{' set on parameter '}
</Typography>
<StringTruncator
maxWidth="300"
text={text}
maxLength={50}
/>
</>
}
/>
</Box>
</Box>
<ConditionallyRender
condition={Boolean(requiredError)}
show={<CancelOutlined color={'error'} />}
elseShow={<div />}
/>
</StyledWrapper>
);
};

View File

@ -4,23 +4,18 @@ import {
parseParameterString,
parseParameterStrings,
} from 'utils/parseParameter';
import { PlaygroundParameterItem } from '../PlaygroundParameterItem/PlaygroundParameterItem';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import { Chip } from '@mui/material';
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
import { PlaygroundConstraintSchema } from 'component/playground/Playground/interfaces/playground.model';
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
import { CustomParameterItem } from './CustomParameterItem/CustomParameterItem';
interface ICustomStrategyProps {
parameters: { [key: string]: string };
strategyName: string;
constraints: PlaygroundConstraintSchema[];
}
export const CustomStrategyParams: VFC<ICustomStrategyProps> = ({
strategyName,
constraints,
parameters,
}) => {
const { strategies } = useStrategies();
@ -32,109 +27,84 @@ export const CustomStrategyParams: VFC<ICustomStrategyProps> = ({
return null;
}
const renderCustomStrategyParameters = () => {
return definition?.parameters.map((param: any, index: number) => {
const notLastItem = index !== definition?.parameters?.length - 1;
switch (param?.type) {
case 'list':
const values = parseParameterStrings(
parameters[param.name]
);
return (
<Fragment key={param?.name}>
<PlaygroundParameterItem
value={values}
text={param.name}
/>
<ConditionallyRender
condition={notLastItem}
show={<StrategySeparator text="AND" />}
/>
</Fragment>
);
case 'percentage':
return (
<Fragment key={param?.name}>
<div>
<Chip
size="small"
variant="outlined"
color="success"
label={`${parameters[param.name]}%`}
/>{' '}
of your base{' '}
{constraints?.length > 0
? 'who match constraints'
: ''}{' '}
is included.
</div>
<PercentageCircle
percentage={parseParameterNumber(
parameters[param.name]
)}
/>
<ConditionallyRender
condition={notLastItem}
show={<StrategySeparator text="AND" />}
/>
</Fragment>
);
case 'boolean':
const bool = Boolean(parameters[param?.name]);
return (
<Fragment key={param?.name}>
<PlaygroundParameterItem
value={bool ? ['True'] : []}
text={param.name}
showReason={!bool}
input={bool ? bool : 'no value'}
/>
<ConditionallyRender
condition={notLastItem}
show={<StrategySeparator text="AND" />}
/>
</Fragment>
);
case 'string':
const value =
parseParameterString(parameters[param.name]) ??
'no value';
return (
<Fragment key={param?.name}>
<PlaygroundParameterItem
value={value !== '' ? [value] : []}
text={param.name}
showReason={value === ''}
input={value !== '' ? value : 'no value'}
/>
<ConditionallyRender
condition={notLastItem}
show={<StrategySeparator text="AND" />}
/>
</Fragment>
);
case 'number':
const number = parseParameterNumber(parameters[param.name]);
return (
<Fragment key={param?.name}>
<PlaygroundParameterItem
value={Boolean(number) ? [number] : []}
text={param.name}
showReason={Boolean(number)}
input={Boolean(number) ? number : 'no value'}
/>
<ConditionallyRender
condition={notLastItem}
show={<StrategySeparator text="AND" />}
/>
</Fragment>
);
case 'default':
return null;
}
return null;
});
};
const items = definition?.parameters.map(param => {
const paramValue = parameters[param.name];
const isRequired = param.required;
return <>{renderCustomStrategyParameters()}</>;
switch (param?.type) {
case 'list':
const values = parseParameterStrings(paramValue);
return (
<CustomParameterItem
isRequired={isRequired}
text={param.name}
input={values?.length > 0 ? values.join(', ') : null}
/>
);
case 'percentage':
const percentage = parseParameterNumber(paramValue);
const correctPercentage = !(
paramValue === undefined ||
paramValue === '' ||
percentage < 0 ||
percentage > 100
);
return (
<CustomParameterItem
text={param.name}
isRequired={isRequired}
input={correctPercentage ? `${percentage}%` : undefined}
/>
);
case 'boolean':
const bool = ['true', 'false'].includes(paramValue)
? paramValue
: undefined;
return (
<CustomParameterItem
isRequired={isRequired}
text={param.name}
input={paramValue !== undefined ? bool : undefined}
/>
);
case 'string':
const value = parseParameterString(paramValue);
return (
<CustomParameterItem
text={param.name}
isRequired={isRequired}
input={value !== undefined ? value : undefined}
/>
);
case 'number':
const isCorrect = !(
paramValue === undefined || paramValue === ''
);
const number = parseParameterNumber(paramValue);
return (
<CustomParameterItem
text={param.name}
isRequired={isRequired}
input={isCorrect ? `${number}` : undefined}
/>
);
case 'default':
return null;
}
return null;
});
return (
<>
{items.map((item, index) => (
<Fragment key={index}>
<ConditionallyRender
condition={index > 0}
show={<StrategySeparator text="AND" />}
/>
{item}
</Fragment>
))}
</>
);
};

View File

@ -4,8 +4,8 @@ export const useStyles = makeStyles()(theme => ({
container: {
width: '100%',
padding: theme.spacing(2, 3),
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadiusMedium,
border: `1px solid ${theme.palette.dividerAlternative}`,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',

View File

@ -13,7 +13,7 @@ export const useStyles = makeStyles()(theme => ({
width: 'auto',
height: 'auto',
padding: theme.spacing(2, 3),
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadiusMedium,
border: `1px solid ${theme.palette.dividerAlternative}`,
},
}));

View File

@ -23,10 +23,6 @@ const StyledStrategyExecutionWrapper = styled('div')(({ theme }) => ({
padding: theme.spacing(0),
}));
const StyledParamWrapper = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 0),
}));
export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
strategyResult,
input,
@ -93,20 +89,15 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
</Box>
}
/>
<StyledParamWrapper>
<PlaygroundResultStrategyExecutionParameters
parameters={parameters}
constraints={constraints}
input={input}
/>
<StyledParamWrapper sx={{ pt: 2 }}>
<CustomStrategyParams
strategyName={strategyResult.name}
parameters={parameters}
constraints={constraints}
/>
</StyledParamWrapper>
</StyledParamWrapper>
<PlaygroundResultStrategyExecutionParameters
parameters={parameters}
constraints={constraints}
input={input}
/>
<CustomStrategyParams
strategyName={strategyResult.name}
parameters={parameters}
/>
</StyledStrategyExecutionWrapper>
);
};

View File

@ -18,16 +18,16 @@ const StyledChipWrapper = styled(Box)(() => ({
}));
export const FeatureStatusCell = ({ feature }: IFeatureStatusCellProps) => {
const enabled = feature.isEnabled
? true
: feature.strategies?.result === false
? false
: 'unknown';
const label = feature.isEnabled
? 'True'
: feature.strategies?.result === false
? 'False'
: 'Unknown';
const [enabled, label]: [boolean | 'unknown', string] = (() => {
if (feature?.isEnabled) {
return [true, 'True'];
}
if (feature?.strategies?.result === false) {
return [false, 'False'];
}
return ['unknown', 'Unknown'];
})();
return (
<StyledCellBox>
<StyledChipWrapper data-loading>
@ -35,7 +35,6 @@ export const FeatureStatusCell = ({ feature }: IFeatureStatusCellProps) => {
enabled={enabled}
label={label}
showIcon={enabled !== 'unknown'}
size={'medium'}
/>
</StyledChipWrapper>
</StyledCellBox>

View File

@ -1,9 +1,9 @@
import { VFC } from 'react';
import { Chip, styled, useTheme } from '@mui/material';
import { colors } from '../../../../../themes/colors';
import { ConditionallyRender } from '../../../../common/ConditionallyRender/ConditionallyRender';
import React from 'react';
import { ReactComponent as FeatureEnabledIcon } from '../../../../../assets/icons/isenabled-true.svg';
import { ReactComponent as FeatureDisabledIcon } from '../../../../../assets/icons/isenabled-false.svg';
import { colors } from 'themes/colors';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ReactComponent as FeatureEnabledIcon } from 'assets/icons/isenabled-true.svg';
import { ReactComponent as FeatureDisabledIcon } from 'assets/icons/isenabled-false.svg';
import { WarningOutlined } from '@mui/icons-material';
interface IResultChipProps {
@ -11,21 +11,18 @@ interface IResultChipProps {
label: string;
// Result icon - defaults to true
showIcon?: boolean;
size?: 'default' | 'medium' | 'large';
}
export const StyledChip = styled(Chip)<{ width?: number }>(
({ theme, width }) => ({
width: width ?? 60,
height: 24,
borderRadius: theme.shape.borderRadius,
fontWeight: theme.typography.fontWeightMedium,
['& .MuiChip-label']: {
padding: 0,
paddingLeft: theme.spacing(0.5),
},
})
);
export const StyledChip = styled(Chip)(({ theme, icon }) => ({
padding: theme.spacing(0, 1),
height: 24,
borderRadius: theme.shape.borderRadius,
fontWeight: theme.typography.fontWeightMedium,
['& .MuiChip-label']: {
padding: 0,
paddingLeft: Boolean(icon) ? theme.spacing(0.5) : 0,
},
}));
export const StyledFalseChip = styled(StyledChip)(({ theme }) => ({
border: `1px solid ${theme.palette.error.main}`,
@ -60,12 +57,11 @@ export const StyledUnknownChip = styled(StyledChip)(({ theme }) => ({
},
}));
export const PlaygroundResultChip = ({
export const PlaygroundResultChip: VFC<IResultChipProps> = ({
enabled,
label,
showIcon = true,
size = 'default',
}: IResultChipProps) => {
}) => {
const theme = useTheme();
const icon = (
<ConditionallyRender
@ -91,10 +87,6 @@ export const PlaygroundResultChip = ({
/>
);
let chipWidth = 60;
if (size === 'medium') chipWidth = 72;
if (size === 'large') chipWidth = 100;
return (
<ConditionallyRender
condition={enabled === 'unknown' || enabled === 'unevaluated'}
@ -102,7 +94,6 @@ export const PlaygroundResultChip = ({
<StyledUnknownChip
icon={showIcon ? icon : undefined}
label={label}
width={chipWidth}
/>
}
elseShow={
@ -112,14 +103,12 @@ export const PlaygroundResultChip = ({
<StyledTrueChip
icon={showIcon ? icon : undefined}
label={label}
width={chipWidth}
/>
}
elseShow={
<StyledFalseChip
icon={showIcon ? icon : undefined}
label={label}
width={chipWidth}
/>
}
/>

View File

@ -47,10 +47,10 @@ export default createTheme({
bold: 700,
},
shape: {
borderRadius: '4px',
borderRadiusMedium: '8px',
borderRadiusLarge: '12px',
borderRadiusExtraLarge: '20px',
borderRadius: 4,
borderRadiusMedium: 8,
borderRadiusLarge: 12,
borderRadiusExtraLarge: 20,
tableRowHeight: 64,
tableRowHeightCompact: 56,
tableRowHeightDense: 48,

View File

@ -120,9 +120,9 @@ declare module '@mui/material/styles' {
declare module '@mui/system/createTheme/shape' {
interface Shape {
borderRadiusMedium: string;
borderRadiusLarge: string;
borderRadiusExtraLarge: string;
borderRadiusMedium: number;
borderRadiusLarge: number;
borderRadiusExtraLarge: number;
tableRowHeight: number;
tableRowHeightCompact: number;
tableRowHeightDense: number;