mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-21 13:47:39 +02:00
Merge pull request #1168 from Unleash/task/Add_strategy_information_to_playground_results
Task/add strategy information to playground results
This commit is contained in:
commit
2e8ebb1d9e
@ -1,12 +1,12 @@
|
|||||||
import { useEffect, useMemo, useState, VFC } from 'react';
|
import { useEffect, useMemo, useState, VFC } from 'react';
|
||||||
import { useGroups } from 'hooks/api/getters/useGroups/useGroups';
|
import { useGroups } from 'hooks/api/getters/useGroups/useGroups';
|
||||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { IGroup } from 'interfaces/group';
|
import { IGroup } from 'interfaces/group';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { Search } from 'component/common/Search/Search';
|
import { Search } from 'component/common/Search/Search';
|
||||||
import { Button, Grid, useMediaQuery } from '@mui/material';
|
import { Grid, useMediaQuery } from '@mui/material';
|
||||||
import theme from 'themes/theme';
|
import theme from 'themes/theme';
|
||||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
import { TablePlaceholder } from 'component/common/Table';
|
import { TablePlaceholder } from 'component/common/Table';
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Accordion, AccordionSummary, AccordionDetails } from '@mui/material';
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
SxProps,
|
||||||
|
Theme,
|
||||||
|
} from '@mui/material';
|
||||||
import { IConstraint } from 'interfaces/strategy';
|
import { IConstraint } from 'interfaces/strategy';
|
||||||
import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody';
|
import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody';
|
||||||
import { ConstraintAccordionViewHeader } from './ConstraintAccordionViewHeader/ConstraintAccordionViewHeader';
|
import { ConstraintAccordionViewHeader } from './ConstraintAccordionViewHeader/ConstraintAccordionViewHeader';
|
||||||
@ -15,12 +21,14 @@ interface IConstraintAccordionViewProps {
|
|||||||
constraint: IConstraint;
|
constraint: IConstraint;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
onEdit?: () => void;
|
onEdit?: () => void;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConstraintAccordionView = ({
|
export const ConstraintAccordionView = ({
|
||||||
constraint,
|
constraint,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
sx = undefined,
|
||||||
}: IConstraintAccordionViewProps) => {
|
}: IConstraintAccordionViewProps) => {
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
const [expandable, setExpandable] = useState(true);
|
const [expandable, setExpandable] = useState(true);
|
||||||
@ -30,7 +38,6 @@ export const ConstraintAccordionView = ({
|
|||||||
[...semVerOperators, ...numOperators, ...dateOperators],
|
[...semVerOperators, ...numOperators, ...dateOperators],
|
||||||
constraint.operator
|
constraint.operator
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (expandable) {
|
if (expandable) {
|
||||||
setExpanded(!expanded);
|
setExpanded(!expanded);
|
||||||
@ -42,6 +49,7 @@ export const ConstraintAccordionView = ({
|
|||||||
className={styles.accordion}
|
className={styles.accordion}
|
||||||
classes={{ root: styles.accordionRoot }}
|
classes={{ root: styles.accordionRoot }}
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
|
sx={sx}
|
||||||
>
|
>
|
||||||
<AccordionSummary
|
<AccordionSummary
|
||||||
classes={{ root: styles.summary }}
|
classes={{ root: styles.summary }}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { styled, Tooltip } from '@mui/material';
|
import { styled, Tooltip } from '@mui/material';
|
||||||
import { ConstraintViewHeaderOperator } from '../ConstraintViewHeaderOperator/ConstraintViewHeaderOperator';
|
import { ConstraintViewHeaderOperator } from '../ConstraintViewHeaderOperator/ConstraintViewHeaderOperator';
|
||||||
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { ConstraintAccordionViewHeaderSingleValue } from '../ContraintAccordionViewHeaderSingleValue/ConstraintAccordionViewHeaderSingleValue';
|
import { ConstraintAccordionViewHeaderSingleValue } from '../ContraintAccordionViewHeaderSingleValue/ConstraintAccordionViewHeaderSingleValue';
|
||||||
import { ConstraintAccordionViewHeaderMultipleValues } from '../ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues';
|
import { ConstraintAccordionViewHeaderMultipleValues } from '../ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -27,11 +27,19 @@ const StyledHeaderText = styled('span')(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledHeaderWrapper = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
borderRadius: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
interface ConstraintAccordionViewHeaderMetaInfoProps {
|
interface ConstraintAccordionViewHeaderMetaInfoProps {
|
||||||
constraint: IConstraint;
|
constraint: IConstraint;
|
||||||
singleValue: boolean;
|
singleValue: boolean;
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
allowExpand: (shouldExpand: boolean) => void;
|
allowExpand: (shouldExpand: boolean) => void;
|
||||||
|
maxLength?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConstraintAccordionViewHeaderInfo = ({
|
export const ConstraintAccordionViewHeaderInfo = ({
|
||||||
@ -39,31 +47,37 @@ export const ConstraintAccordionViewHeaderInfo = ({
|
|||||||
singleValue,
|
singleValue,
|
||||||
allowExpand,
|
allowExpand,
|
||||||
expanded,
|
expanded,
|
||||||
|
maxLength = 112, //The max number of characters in the values text for NOT allowing expansion
|
||||||
}: ConstraintAccordionViewHeaderMetaInfoProps) => {
|
}: ConstraintAccordionViewHeaderMetaInfoProps) => {
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.headerMetaInfo}>
|
<StyledHeaderWrapper>
|
||||||
<Tooltip title={constraint.contextName} arrow>
|
<div className={styles.headerMetaInfo}>
|
||||||
<StyledHeaderText>{constraint.contextName}</StyledHeaderText>
|
<Tooltip title={constraint.contextName} arrow>
|
||||||
</Tooltip>
|
<StyledHeaderText>
|
||||||
<ConstraintViewHeaderOperator constraint={constraint} />
|
{constraint.contextName}
|
||||||
<ConditionallyRender
|
</StyledHeaderText>
|
||||||
condition={singleValue}
|
</Tooltip>
|
||||||
show={
|
<ConstraintViewHeaderOperator constraint={constraint} />
|
||||||
<ConstraintAccordionViewHeaderSingleValue
|
<ConditionallyRender
|
||||||
constraint={constraint}
|
condition={singleValue}
|
||||||
allowExpand={allowExpand}
|
show={
|
||||||
/>
|
<ConstraintAccordionViewHeaderSingleValue
|
||||||
}
|
constraint={constraint}
|
||||||
elseShow={
|
allowExpand={allowExpand}
|
||||||
<ConstraintAccordionViewHeaderMultipleValues
|
/>
|
||||||
constraint={constraint}
|
}
|
||||||
expanded={expanded}
|
elseShow={
|
||||||
allowExpand={allowExpand}
|
<ConstraintAccordionViewHeaderMultipleValues
|
||||||
maxLength={112}
|
constraint={constraint}
|
||||||
/>
|
expanded={expanded}
|
||||||
}
|
allowExpand={allowExpand}
|
||||||
/>
|
maxLength={maxLength}
|
||||||
</div>
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledHeaderWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Chip, styled } from '@mui/material';
|
import { Chip, styled } from '@mui/material';
|
||||||
import { formatConstraintValue } from '../../../../../../utils/formatConstraintValue';
|
import { formatConstraintValue } from 'utils/formatConstraintValue';
|
||||||
import { useStyles } from '../../../ConstraintAccordion.styles';
|
import { useStyles } from '../../../ConstraintAccordion.styles';
|
||||||
import { IConstraint } from '../../../../../../interfaces/strategy';
|
import { IConstraint } from '../../../../../../interfaces/strategy';
|
||||||
import { useLocationSettings } from '../../../../../../hooks/useLocationSettings';
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
|
|
||||||
const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
|
const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
|
||||||
margin: 'auto 0',
|
margin: 'auto 0',
|
||||||
|
@ -26,6 +26,11 @@ export const useStyles = makeStyles()(theme => ({
|
|||||||
actions: {
|
actions: {
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
minHeight: theme.spacing(6),
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
resultChip: {
|
||||||
|
marginLeft: 'auto',
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(2),
|
@ -0,0 +1,96 @@
|
|||||||
|
import { DragEventHandler, FC, ReactNode } from 'react';
|
||||||
|
import { DragIndicator } from '@mui/icons-material';
|
||||||
|
import { styled, IconButton, Box } from '@mui/material';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
import {
|
||||||
|
getFeatureStrategyIcon,
|
||||||
|
formatStrategyName,
|
||||||
|
} from 'utils/strategyNames';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { useStyles } from './StrategyItemContainer.styles';
|
||||||
|
|
||||||
|
interface IStrategyItemContainerProps {
|
||||||
|
strategy: IFeatureStrategy;
|
||||||
|
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
||||||
|
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
||||||
|
actions?: ReactNode;
|
||||||
|
orderNumber?: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DragIcon = styled(IconButton)(({ theme }) => ({
|
||||||
|
padding: 0,
|
||||||
|
cursor: 'inherit',
|
||||||
|
transition: 'color 0.2s ease-in-out',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledIndexLabel = styled('div')(({ theme }) => ({
|
||||||
|
fontSize: theme.typography.fontSize,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
position: 'absolute',
|
||||||
|
display: 'none',
|
||||||
|
right: 'calc(100% + 6px)',
|
||||||
|
top: theme.spacing(2.5),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
display: 'block',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
|
||||||
|
strategy,
|
||||||
|
onDragStart,
|
||||||
|
onDragEnd,
|
||||||
|
actions,
|
||||||
|
children,
|
||||||
|
orderNumber,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
const Icon = getFeatureStrategyIcon(strategy.name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ position: 'relative' }}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={orderNumber !== undefined}
|
||||||
|
show={<StyledIndexLabel>{orderNumber}</StyledIndexLabel>}
|
||||||
|
/>
|
||||||
|
<Box className={classNames(styles.container, className)}>
|
||||||
|
<div
|
||||||
|
className={classNames(styles.header, {
|
||||||
|
[styles.headerDraggable]: Boolean(onDragStart),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<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: 'neutral.main' }}
|
||||||
|
/>
|
||||||
|
</DragIcon>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Icon className={styles.icon} />
|
||||||
|
<StringTruncator
|
||||||
|
maxWidth="150"
|
||||||
|
maxLength={15}
|
||||||
|
text={formatStrategyName(strategy.name)}
|
||||||
|
/>
|
||||||
|
<div className={styles.actions}>{actions}</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.body}>{children}</div>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
|
|
||||||
interface IIconCellProps {
|
interface IIconCellProps {
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Box, styled } from '@mui/material';
|
import { DragEventHandler, RefObject, useRef } from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
import { DragEventHandler, RefObject, useRef } from 'react';
|
|
||||||
import { StrategyItem } from './StrategyItem/StrategyItem';
|
import { StrategyItem } from './StrategyItem/StrategyItem';
|
||||||
|
|
||||||
interface IStrategyDraggableItemProps {
|
interface IStrategyDraggableItemProps {
|
||||||
@ -22,19 +22,6 @@ interface IStrategyDraggableItemProps {
|
|||||||
) => DragEventHandler<HTMLDivElement>;
|
) => DragEventHandler<HTMLDivElement>;
|
||||||
onDragEnd: () => void;
|
onDragEnd: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledIndexLabel = styled('div')(({ theme }) => ({
|
|
||||||
fontSize: theme.typography.fontSize,
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
position: 'absolute',
|
|
||||||
display: 'none',
|
|
||||||
right: 'calc(100% + 6px)',
|
|
||||||
top: theme.spacing(2.5),
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
display: 'block',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const StrategyDraggableItem = ({
|
export const StrategyDraggableItem = ({
|
||||||
strategy,
|
strategy,
|
||||||
index,
|
index,
|
||||||
@ -58,16 +45,15 @@ export const StrategyDraggableItem = ({
|
|||||||
condition={index > 0}
|
condition={index > 0}
|
||||||
show={<StrategySeparator text="OR" />}
|
show={<StrategySeparator text="OR" />}
|
||||||
/>
|
/>
|
||||||
<Box sx={{ position: 'relative' }}>
|
|
||||||
<StyledIndexLabel>{index + 1}</StyledIndexLabel>
|
<StrategyItem
|
||||||
<StrategyItem
|
strategy={strategy}
|
||||||
strategy={strategy}
|
environmentId={environmentName}
|
||||||
environmentId={environmentName}
|
otherEnvironments={otherEnvironments}
|
||||||
otherEnvironments={otherEnvironments}
|
onDragStart={onDragStartRef(ref, index)}
|
||||||
onDragStart={onDragStartRef(ref, index)}
|
onDragEnd={onDragEnd}
|
||||||
onDragEnd={onDragEnd}
|
orderNumber={index + 1}
|
||||||
/>
|
/>
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,6 @@ import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
|||||||
|
|
||||||
interface IStrategyExecutionProps {
|
interface IStrategyExecutionProps {
|
||||||
strategy: IFeatureStrategy;
|
strategy: IFeatureStrategy;
|
||||||
percentageFill?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NoItems: VFC = () => (
|
const NoItems: VFC = () => (
|
||||||
|
@ -1,24 +1,17 @@
|
|||||||
import { DragEventHandler } from 'react';
|
import { DragEventHandler, VFC } from 'react';
|
||||||
import { DragIndicator, Edit } from '@mui/icons-material';
|
import { Edit } from '@mui/icons-material';
|
||||||
import { styled, useTheme, IconButton } from '@mui/material';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
import {
|
|
||||||
getFeatureStrategyIcon,
|
|
||||||
formatStrategyName,
|
|
||||||
} from 'utils/strategyNames';
|
|
||||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||||
import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
|
import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
|
||||||
import { FeatureStrategyRemove } from 'component/feature/FeatureStrategy/FeatureStrategyRemove/FeatureStrategyRemove';
|
import { FeatureStrategyRemove } from 'component/feature/FeatureStrategy/FeatureStrategyRemove/FeatureStrategyRemove';
|
||||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
|
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
|
||||||
import { useStyles } from './StrategyItem.styles';
|
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
||||||
|
|
||||||
interface IStrategyItemProps {
|
interface IStrategyItemProps {
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
@ -26,26 +19,19 @@ interface IStrategyItemProps {
|
|||||||
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
||||||
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
||||||
otherEnvironments?: IFeatureEnvironment['name'][];
|
otherEnvironments?: IFeatureEnvironment['name'][];
|
||||||
|
orderNumber?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DragIcon = styled(IconButton)(({ theme }) => ({
|
export const StrategyItem: VFC<IStrategyItemProps> = ({
|
||||||
padding: 0,
|
|
||||||
cursor: 'inherit',
|
|
||||||
transition: 'color 0.2s ease-in-out',
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const StrategyItem = ({
|
|
||||||
environmentId,
|
environmentId,
|
||||||
strategy,
|
strategy,
|
||||||
onDragStart,
|
onDragStart,
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
otherEnvironments,
|
otherEnvironments,
|
||||||
}: IStrategyItemProps) => {
|
orderNumber,
|
||||||
|
}) => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const theme = useTheme();
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const Icon = getFeatureStrategyIcon(strategy.name);
|
|
||||||
|
|
||||||
const editStrategyPath = formatEditStrategyPath(
|
const editStrategyPath = formatEditStrategyPath(
|
||||||
projectId,
|
projectId,
|
||||||
@ -55,38 +41,13 @@ export const StrategyItem = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<StrategyItemContainer
|
||||||
<div
|
strategy={strategy}
|
||||||
className={classNames(styles.header, {
|
onDragStart={onDragStart}
|
||||||
[styles.headerDraggable]: Boolean(onDragStart),
|
onDragEnd={onDragEnd}
|
||||||
})}
|
orderNumber={orderNumber}
|
||||||
>
|
actions={
|
||||||
<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: 'neutral.main' }}
|
|
||||||
/>
|
|
||||||
</DragIcon>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Icon className={styles.icon} />
|
|
||||||
<StringTruncator
|
|
||||||
maxWidth="150"
|
|
||||||
maxLength={15}
|
|
||||||
text={formatStrategyName(strategy.name)}
|
|
||||||
/>
|
|
||||||
<div className={styles.actions}>
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(
|
condition={Boolean(
|
||||||
otherEnvironments && otherEnvironments?.length > 0
|
otherEnvironments && otherEnvironments?.length > 0
|
||||||
@ -115,14 +76,10 @@ export const StrategyItem = ({
|
|||||||
strategyId={strategy.id}
|
strategyId={strategy.id}
|
||||||
icon
|
icon
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
</div>
|
}
|
||||||
<div className={styles.body}>
|
>
|
||||||
<StrategyExecution
|
<StrategyExecution strategy={strategy} />
|
||||||
strategy={strategy}
|
</StrategyItemContainer>
|
||||||
percentageFill={theme.palette.grey[200]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
|
|||||||
import { PlaygroundResultsTable } from './PlaygroundResultsTable/PlaygroundResultsTable';
|
import { PlaygroundResultsTable } from './PlaygroundResultsTable/PlaygroundResultsTable';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { usePlaygroundApi } from 'hooks/api/actions/usePlayground/usePlayground';
|
import { usePlaygroundApi } from 'hooks/api/actions/usePlayground/usePlayground';
|
||||||
import { PlaygroundResponseSchema } from 'hooks/api/actions/usePlayground/playground.model';
|
import { PlaygroundResponseSchema } from 'component/playground/Playground/interfaces/playground.model';
|
||||||
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
import { PlaygroundForm } from './PlaygroundForm/PlaygroundForm';
|
import { PlaygroundForm } from './PlaygroundForm/PlaygroundForm';
|
||||||
import {
|
import {
|
||||||
@ -203,6 +203,7 @@ export const Playground: VFC<{}> = () => {
|
|||||||
<PlaygroundResultsTable
|
<PlaygroundResultsTable
|
||||||
loading={loading}
|
loading={loading}
|
||||||
features={results?.features}
|
features={results?.features}
|
||||||
|
input={results?.input}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
elseShow={<PlaygroundGuidance />}
|
elseShow={<PlaygroundGuidance />}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { colors } from 'themes/colors';
|
import { colors } from 'themes/colors';
|
||||||
import { Alert, styled } from '@mui/material';
|
import { Alert, styled } from '@mui/material';
|
||||||
import { SdkContextSchema } from 'hooks/api/actions/usePlayground/playground.model';
|
import { SdkContextSchema } from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
|
||||||
interface IContextBannerProps {
|
interface IContextBannerProps {
|
||||||
environment: string;
|
environment: string;
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles()(theme => ({
|
||||||
|
titleRowWrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
titleRow: {
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: theme.spacing(1.5),
|
||||||
|
marginTop: theme.spacing(1.5),
|
||||||
|
},
|
||||||
|
alertRow: {
|
||||||
|
margin: theme.spacing(1, 0),
|
||||||
|
},
|
||||||
|
descriptionRow: {
|
||||||
|
margin: theme.spacing(1, 0.5),
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
fontWeight: 600,
|
||||||
|
padding: theme.spacing(0.5),
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,109 @@
|
|||||||
|
import {
|
||||||
|
PlaygroundFeatureSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { Alert, IconButton, Typography, useTheme } from '@mui/material';
|
||||||
|
import { PlaygroundResultChip } from '../../PlaygroundResultChip/PlaygroundResultChip';
|
||||||
|
import { useStyles } from './FeatureDetails.styles';
|
||||||
|
import { CloseOutlined } from '@mui/icons-material';
|
||||||
|
import React from 'react';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import {
|
||||||
|
checkForEmptyValues,
|
||||||
|
hasCustomStrategies,
|
||||||
|
hasOnlyCustomStrategies,
|
||||||
|
} from './helpers';
|
||||||
|
|
||||||
|
interface PlaygroundFeatureResultDetailsProps {
|
||||||
|
feature: PlaygroundFeatureSchema;
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
export const FeatureDetails = ({
|
||||||
|
feature,
|
||||||
|
input,
|
||||||
|
onClose,
|
||||||
|
}: PlaygroundFeatureResultDetailsProps) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const description = feature.isEnabled
|
||||||
|
? `This feature toggle is True in ${input?.environment} because `
|
||||||
|
: `This feature toggle is False in ${input?.environment} because `;
|
||||||
|
|
||||||
|
const reason = (() => {
|
||||||
|
if (feature.isEnabled) return 'at least one strategy is True';
|
||||||
|
|
||||||
|
if (!feature.isEnabledInCurrentEnvironment)
|
||||||
|
return 'the environment is disabled';
|
||||||
|
|
||||||
|
if (hasOnlyCustomStrategies(feature))
|
||||||
|
return 'no strategies could be fully evaluated';
|
||||||
|
|
||||||
|
return 'all strategies are either False or could not be fully evaluated';
|
||||||
|
})();
|
||||||
|
|
||||||
|
const color = feature.isEnabled
|
||||||
|
? theme.palette.success.main
|
||||||
|
: theme.palette.error.main;
|
||||||
|
|
||||||
|
const noValueTxt = checkForEmptyValues(input?.context)
|
||||||
|
? 'You did not provide a value for your context field in step 2 of the configuration'
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const customStrategiesTxt = hasCustomStrategies(feature)
|
||||||
|
? `This feature uses custom strategies. Custom strategies can't be evaluated, so they will be marked as Unevaluated`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const onCloseClick =
|
||||||
|
onClose &&
|
||||||
|
((event: React.SyntheticEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.titleRowWrapper}>
|
||||||
|
<div className={styles.titleRow}>
|
||||||
|
<Typography variant={'subtitle1'} className={styles.name}>
|
||||||
|
{feature.name}
|
||||||
|
</Typography>
|
||||||
|
<span>
|
||||||
|
<PlaygroundResultChip
|
||||||
|
enabled={feature.isEnabled}
|
||||||
|
label={feature.isEnabled ? 'True' : 'False'}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<IconButton onClick={onCloseClick} className={styles.icon}>
|
||||||
|
<CloseOutlined />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
<div className={styles.descriptionRow}>
|
||||||
|
<Typography variant="body1" component="span">
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="subtitle1" color={color} component="span">
|
||||||
|
{reason}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(noValueTxt)}
|
||||||
|
show={
|
||||||
|
<div className={styles.alertRow}>
|
||||||
|
<Alert color={'info'}>{noValueTxt}</Alert>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(customStrategiesTxt)}
|
||||||
|
show={
|
||||||
|
<div className={styles.alertRow}>
|
||||||
|
<Alert color={'info'}>{customStrategiesTxt}</Alert>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
import { PlaygroundFeatureSchema } from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
|
||||||
|
export const DEFAULT_STRATEGIES = [
|
||||||
|
'default',
|
||||||
|
'applicationHostname',
|
||||||
|
'flexibleRollout',
|
||||||
|
'gradualRolloutRandom',
|
||||||
|
'gradualRolloutSessionId',
|
||||||
|
'gradualRolloutUserId',
|
||||||
|
'remoteAddress',
|
||||||
|
'userWithId',
|
||||||
|
];
|
||||||
|
|
||||||
|
export function checkForEmptyValues(object?: Object): boolean {
|
||||||
|
if (object === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Object.values(object).every(v =>
|
||||||
|
v && typeof v === 'object' ? checkForEmptyValues(v) : v === null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasCustomStrategies = (feature: PlaygroundFeatureSchema) => {
|
||||||
|
return feature.strategies?.data?.find(
|
||||||
|
strategy => !DEFAULT_STRATEGIES.includes(strategy.name)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasOnlyCustomStrategies = (feature: PlaygroundFeatureSchema) => {
|
||||||
|
return !feature.strategies?.data?.find(strategy =>
|
||||||
|
DEFAULT_STRATEGIES.includes(strategy.name)
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles()(theme => ({
|
||||||
|
popoverPaper: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: theme.spacing(6),
|
||||||
|
width: 728,
|
||||||
|
maxWidth: '100%',
|
||||||
|
height: 'auto',
|
||||||
|
overflowY: 'auto',
|
||||||
|
backgroundColor: theme.palette.tertiary.light,
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
PlaygroundFeatureSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { IconButton, Popover, styled } from '@mui/material';
|
||||||
|
import { InfoOutlined } from '@mui/icons-material';
|
||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import { useStyles } from './FeatureResultInfoPopoverCell.styles';
|
||||||
|
import { FeatureDetails } from './FeatureDetails/FeatureDetails';
|
||||||
|
import { PlaygroundResultFeatureStrategyList } from './FeatureStrategyList/PlaygroundResultFeatureStrategyList';
|
||||||
|
|
||||||
|
interface FeatureResultInfoPopoverCellProps {
|
||||||
|
feature: PlaygroundFeatureSchema;
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FeatureResultPopoverWrapper = styled('div')(({ theme }) => ({
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
color: theme.palette.tertiary.main,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const FeatureResultInfoPopoverCell = ({
|
||||||
|
feature,
|
||||||
|
input,
|
||||||
|
}: FeatureResultInfoPopoverCellProps) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
|
const togglePopover = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!feature) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FeatureResultPopoverWrapper>
|
||||||
|
<IconButton onClick={togglePopover}>
|
||||||
|
<InfoOutlined ref={ref} />
|
||||||
|
</IconButton>
|
||||||
|
<Popover
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
anchorEl={ref.current}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'center',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
classes={{ paper: styles.popoverPaper }}
|
||||||
|
>
|
||||||
|
<FeatureDetails
|
||||||
|
feature={feature}
|
||||||
|
input={input}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
/>
|
||||||
|
<PlaygroundResultFeatureStrategyList
|
||||||
|
feature={feature}
|
||||||
|
input={input}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
</FeatureResultPopoverWrapper>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,39 @@
|
|||||||
|
import { PlaygroundResultStrategyLists } from './StrategyList/playgroundResultStrategyLists';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import {
|
||||||
|
PlaygroundFeatureSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { Alert } from '@mui/material';
|
||||||
|
|
||||||
|
interface PlaygroundResultFeatureStrategyListProps {
|
||||||
|
feature: PlaygroundFeatureSchema;
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaygroundResultFeatureStrategyList = ({
|
||||||
|
feature,
|
||||||
|
input,
|
||||||
|
}: PlaygroundResultFeatureStrategyListProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
!feature.isEnabledInCurrentEnvironment &&
|
||||||
|
Boolean(feature?.strategies?.data)
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<Alert severity={'info'} color={'info'}>
|
||||||
|
If environment would be enabled then this feature would
|
||||||
|
be {feature.strategies?.result ? 'TRUE' : 'FALSE'} and
|
||||||
|
the strategies would evaluate like this:{' '}
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<PlaygroundResultStrategyLists
|
||||||
|
strategies={feature?.strategies?.data || []}
|
||||||
|
input={input}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,40 @@
|
|||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles()(theme => ({
|
||||||
|
header: {
|
||||||
|
display: 'flex',
|
||||||
|
padding: theme.spacing(2, 2),
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
headerName: {
|
||||||
|
padding: theme.spacing(0.5, 2),
|
||||||
|
display: 'flex',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
alignItems: 'center',
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
fill: theme.palette.inactiveIcon,
|
||||||
|
},
|
||||||
|
resultChip: {
|
||||||
|
marginLeft: 'auto',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
justifyItems: 'center',
|
||||||
|
},
|
||||||
|
innerContainer: {
|
||||||
|
[theme.breakpoints.down(400)]: {
|
||||||
|
padding: '0.5rem',
|
||||||
|
},
|
||||||
|
width: '100%',
|
||||||
|
flexShrink: 0,
|
||||||
|
paddingBottom: '1rem',
|
||||||
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
|
background: theme.palette.background.default,
|
||||||
|
},
|
||||||
|
successBorder: {
|
||||||
|
border: `1px solid ${theme.palette.success.main}`,
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,62 @@
|
|||||||
|
import { useTheme } from '@mui/material';
|
||||||
|
import { PlaygroundResultChip } from '../../../../PlaygroundResultChip/PlaygroundResultChip';
|
||||||
|
import {
|
||||||
|
PlaygroundStrategySchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
||||||
|
import { useStyles } from './FeatureStrategyItem.styles';
|
||||||
|
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
||||||
|
import { objectId } from 'utils/objectId';
|
||||||
|
|
||||||
|
interface IFeatureStrategyItemProps {
|
||||||
|
strategy: PlaygroundStrategySchema;
|
||||||
|
index: number;
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FeatureStrategyItem = ({
|
||||||
|
strategy,
|
||||||
|
input,
|
||||||
|
index,
|
||||||
|
}: IFeatureStrategyItemProps) => {
|
||||||
|
const { result } = strategy;
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
const label =
|
||||||
|
result.evaluationStatus === 'incomplete'
|
||||||
|
? 'Unevaluated'
|
||||||
|
: result.enabled
|
||||||
|
? 'True'
|
||||||
|
: 'False';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StrategyItemContainer
|
||||||
|
className={
|
||||||
|
result.enabled && result.evaluationStatus === 'complete'
|
||||||
|
? styles.successBorder
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
strategy={{ ...strategy, id: `${objectId(strategy)}` }}
|
||||||
|
orderNumber={index + 1}
|
||||||
|
actions={
|
||||||
|
<PlaygroundResultChip
|
||||||
|
showIcon={false}
|
||||||
|
enabled={result.enabled}
|
||||||
|
label={label}
|
||||||
|
size={
|
||||||
|
result.evaluationStatus === 'incomplete'
|
||||||
|
? 'large'
|
||||||
|
: 'default'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StrategyExecution
|
||||||
|
strategyResult={strategy}
|
||||||
|
input={input}
|
||||||
|
percentageFill={theme.palette.tertiary.light}
|
||||||
|
/>
|
||||||
|
</StrategyItemContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,106 @@
|
|||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles()(theme => ({
|
||||||
|
constraintIconContainer: {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
borderRadius: '50%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
[theme.breakpoints.down(650)]: {
|
||||||
|
marginBottom: '1rem',
|
||||||
|
marginRight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
constraintIcon: {
|
||||||
|
fill: '#fff',
|
||||||
|
},
|
||||||
|
accordion: {
|
||||||
|
border: `1px solid ${theme.palette.dividerAlternative}`,
|
||||||
|
borderRadius: theme.spacing(1),
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
boxShadow: 'none',
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
accordionRoot: {
|
||||||
|
'&:before': {
|
||||||
|
opacity: '0 !important',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headerMetaInfo: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
[theme.breakpoints.down(710)]: { flexDirection: 'column' },
|
||||||
|
},
|
||||||
|
headerContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
[theme.breakpoints.down(710)]: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headerValuesContainerWrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
margin: 'auto 0',
|
||||||
|
},
|
||||||
|
headerValuesContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'stretch',
|
||||||
|
margin: 'auto 0',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
headerValues: {
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
},
|
||||||
|
headerValuesExpand: {
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
marginTop: '4px',
|
||||||
|
color: theme.palette.primary.dark,
|
||||||
|
[theme.breakpoints.down(710)]: {
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headerConstraintContainer: {
|
||||||
|
minWidth: '220px',
|
||||||
|
position: 'relative',
|
||||||
|
paddingRight: '1rem',
|
||||||
|
[theme.breakpoints.between(1101, 1365)]: {
|
||||||
|
minWidth: '152px',
|
||||||
|
paddingRight: '0.5rem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headerText: {
|
||||||
|
maxWidth: '400px',
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
[theme.breakpoints.down('xl')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
chip: {
|
||||||
|
margin: '0 0.5rem 0.5rem 0',
|
||||||
|
},
|
||||||
|
chipValue: {
|
||||||
|
whiteSpace: 'pre',
|
||||||
|
},
|
||||||
|
accordionDetails: {
|
||||||
|
borderTop: `1px dashed ${theme.palette.grey[300]}`,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
valuesContainer: {
|
||||||
|
padding: '1rem 0rem',
|
||||||
|
maxHeight: '400px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
border: 'none',
|
||||||
|
padding: theme.spacing(0.5, 3),
|
||||||
|
'&:hover .valuesExpandLabel': {
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,91 @@
|
|||||||
|
import { useState, VFC } from 'react';
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
SxProps,
|
||||||
|
Theme,
|
||||||
|
useTheme,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { ConstraintAccordionViewHeader } from './ConstraintAccordionViewHeader/ConstraintAccordionViewHeader';
|
||||||
|
import { oneOf } from 'utils/oneOf';
|
||||||
|
import {
|
||||||
|
dateOperators,
|
||||||
|
numOperators,
|
||||||
|
semVerOperators,
|
||||||
|
} from 'constants/operators';
|
||||||
|
import { useStyles } from './ConstraintAccordion.styles';
|
||||||
|
import {
|
||||||
|
PlaygroundConstraintSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { ConstraintAccordionViewBody } from 'component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody';
|
||||||
|
|
||||||
|
interface IConstraintAccordionViewProps {
|
||||||
|
constraint: PlaygroundConstraintSchema;
|
||||||
|
playgroundInput?: PlaygroundRequestSchema;
|
||||||
|
maxLength?: number;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConstraintAccordionView: VFC<IConstraintAccordionViewProps> = ({
|
||||||
|
constraint,
|
||||||
|
sx = undefined,
|
||||||
|
maxLength,
|
||||||
|
playgroundInput,
|
||||||
|
}) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
const [expandable, setExpandable] = useState(true);
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const singleValue = oneOf(
|
||||||
|
[...semVerOperators, ...numOperators, ...dateOperators],
|
||||||
|
constraint.operator
|
||||||
|
);
|
||||||
|
const handleClick = () => {
|
||||||
|
if (expandable) {
|
||||||
|
setExpanded(!expanded);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const backgroundColor = Boolean(playgroundInput)
|
||||||
|
? !Boolean((constraint as PlaygroundConstraintSchema).result)
|
||||||
|
? theme.palette.neutral.light
|
||||||
|
: 'inherit'
|
||||||
|
: 'inherit';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion
|
||||||
|
className={styles.accordion}
|
||||||
|
classes={{ root: styles.accordionRoot }}
|
||||||
|
expanded={expanded}
|
||||||
|
sx={sx}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
classes={{ root: styles.summary }}
|
||||||
|
expandIcon={null}
|
||||||
|
onClick={handleClick}
|
||||||
|
sx={{
|
||||||
|
cursor: expandable ? 'pointer' : 'default!important',
|
||||||
|
'&:hover': {
|
||||||
|
cursor: expandable ? 'pointer' : 'default!important',
|
||||||
|
},
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ConstraintAccordionViewHeader
|
||||||
|
constraint={constraint}
|
||||||
|
singleValue={singleValue}
|
||||||
|
allowExpand={setExpandable}
|
||||||
|
expanded={expanded}
|
||||||
|
maxLength={maxLength ?? 112}
|
||||||
|
playgroundInput={playgroundInput}
|
||||||
|
/>
|
||||||
|
</AccordionSummary>
|
||||||
|
|
||||||
|
<AccordionDetails className={styles.accordionDetails}>
|
||||||
|
<ConstraintAccordionViewBody constraint={constraint} />
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,42 @@
|
|||||||
|
import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon';
|
||||||
|
import { ConstraintAccordionViewHeaderInfo } from './ConstraintAccordionViewHeaderInfo/ConstraintAccordionViewHeaderInfo';
|
||||||
|
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
|
||||||
|
import {
|
||||||
|
PlaygroundConstraintSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
|
||||||
|
interface PlaygroundConstraintAccordionViewHeaderProps {
|
||||||
|
constraint: PlaygroundConstraintSchema;
|
||||||
|
singleValue: boolean;
|
||||||
|
expanded: boolean;
|
||||||
|
allowExpand: (shouldExpand: boolean) => void;
|
||||||
|
playgroundInput?: PlaygroundRequestSchema;
|
||||||
|
maxLength?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConstraintAccordionViewHeader = ({
|
||||||
|
constraint,
|
||||||
|
singleValue,
|
||||||
|
allowExpand,
|
||||||
|
expanded,
|
||||||
|
maxLength,
|
||||||
|
playgroundInput,
|
||||||
|
}: PlaygroundConstraintAccordionViewHeaderProps) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.headerContainer}>
|
||||||
|
<ConstraintIcon />
|
||||||
|
<ConstraintAccordionViewHeaderInfo
|
||||||
|
constraint={constraint}
|
||||||
|
singleValue={singleValue}
|
||||||
|
allowExpand={allowExpand}
|
||||||
|
expanded={expanded}
|
||||||
|
result={constraint.result}
|
||||||
|
maxLength={maxLength}
|
||||||
|
playgroundInput={playgroundInput}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,111 @@
|
|||||||
|
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 React from 'react';
|
||||||
|
import { useStyles } from '../../ConstraintAccordion.styles';
|
||||||
|
import { CancelOutlined } from '@mui/icons-material';
|
||||||
|
import {
|
||||||
|
PlaygroundConstraintSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { ConstraintViewHeaderOperator } from 'component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintViewHeaderOperator/ConstraintViewHeaderOperator';
|
||||||
|
|
||||||
|
const StyledHeaderText = styled('span')(({ theme }) => ({
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 3,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
overflow: 'hidden',
|
||||||
|
maxWidth: '100px',
|
||||||
|
minWidth: '100px',
|
||||||
|
marginRight: '10px',
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 'auto',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
[theme.breakpoints.down(710)]: {
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: theme.spacing(1, 0),
|
||||||
|
marginRight: 'inherit',
|
||||||
|
maxWidth: 'inherit',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeaderWrapper = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
borderRadius: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface PlaygroundConstraintAccordionViewHeaderInfoProps {
|
||||||
|
constraint: PlaygroundConstraintSchema;
|
||||||
|
singleValue: boolean;
|
||||||
|
expanded: boolean;
|
||||||
|
allowExpand: (shouldExpand: boolean) => void;
|
||||||
|
result?: boolean;
|
||||||
|
maxLength?: number;
|
||||||
|
playgroundInput?: PlaygroundRequestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConstraintAccordionViewHeaderInfo = ({
|
||||||
|
constraint,
|
||||||
|
singleValue,
|
||||||
|
allowExpand,
|
||||||
|
expanded,
|
||||||
|
result,
|
||||||
|
playgroundInput,
|
||||||
|
maxLength = 112,
|
||||||
|
}: PlaygroundConstraintAccordionViewHeaderInfoProps) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const constraintExistsInContext = Boolean(
|
||||||
|
playgroundInput?.context[constraint.contextName]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledHeaderWrapper>
|
||||||
|
<div className={styles.headerMetaInfo}>
|
||||||
|
<Tooltip title={constraint.contextName} arrow>
|
||||||
|
<StyledHeaderText>
|
||||||
|
{constraint.contextName}
|
||||||
|
<Typography
|
||||||
|
variant={'body1'}
|
||||||
|
color={
|
||||||
|
constraintExistsInContext
|
||||||
|
? theme.palette.neutral.dark
|
||||||
|
: theme.palette.error.main
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{playgroundInput?.context[constraint.contextName] ||
|
||||||
|
'no value'}
|
||||||
|
</Typography>
|
||||||
|
</StyledHeaderText>
|
||||||
|
</Tooltip>
|
||||||
|
<ConstraintViewHeaderOperator constraint={constraint} />
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={singleValue}
|
||||||
|
show={
|
||||||
|
<PlaygroundSingleValue
|
||||||
|
constraint={constraint}
|
||||||
|
allowExpand={allowExpand}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<PLaygroundMultipleValues
|
||||||
|
constraint={constraint}
|
||||||
|
expanded={expanded}
|
||||||
|
allowExpand={allowExpand}
|
||||||
|
maxLength={maxLength}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={result !== undefined && !Boolean(result)}
|
||||||
|
show={<CancelOutlined color="error" sx={{ mt: 1 }} />}
|
||||||
|
/>
|
||||||
|
</StyledHeaderWrapper>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,85 @@
|
|||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { styled, Typography } from '@mui/material';
|
||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { useStyles } from '../../../ConstraintAccordion.styles';
|
||||||
|
import { PlaygroundConstraintSchema } from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
|
||||||
|
const StyledValuesSpan = styled('span')(({ theme }) => ({
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
overflow: 'hidden',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
margin: 'auto 0',
|
||||||
|
[theme.breakpoints.down(710)]: {
|
||||||
|
margin: theme.spacing(1, 0),
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface PLaygroundConstraintAccordionViewHeaderMultipleValueProps {
|
||||||
|
constraint: PlaygroundConstraintSchema;
|
||||||
|
expanded: boolean;
|
||||||
|
maxLength: number;
|
||||||
|
allowExpand: (shouldExpand: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PLaygroundMultipleValues = ({
|
||||||
|
constraint,
|
||||||
|
expanded,
|
||||||
|
allowExpand,
|
||||||
|
maxLength,
|
||||||
|
}: PLaygroundConstraintAccordionViewHeaderMultipleValueProps) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
|
||||||
|
const [expandable, setExpandable] = useState(false);
|
||||||
|
|
||||||
|
const text = useMemo(() => {
|
||||||
|
return constraint?.values?.map(value => value).join(', ');
|
||||||
|
}, [constraint]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (text) {
|
||||||
|
allowExpand((text?.length ?? 0) > maxLength);
|
||||||
|
setExpandable((text?.length ?? 0) > maxLength);
|
||||||
|
}
|
||||||
|
}, [text, maxLength, allowExpand, setExpandable]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.headerValuesContainerWrapper}>
|
||||||
|
<div className={styles.headerValuesContainer}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={!Boolean(constraint.result)}
|
||||||
|
show={
|
||||||
|
<Typography
|
||||||
|
variant={'body2'}
|
||||||
|
color={'error'}
|
||||||
|
noWrap={true}
|
||||||
|
sx={{ mr: 1 }}
|
||||||
|
>
|
||||||
|
does not match any values{' '}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledValuesSpan>{text}</StyledValuesSpan>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={expandable}
|
||||||
|
show={
|
||||||
|
<p
|
||||||
|
className={classnames(
|
||||||
|
styles.headerValuesExpand,
|
||||||
|
'valuesExpandLabel'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!expanded
|
||||||
|
? `View all (${constraint?.values?.length})`
|
||||||
|
: 'View less'}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,47 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Chip, styled, Typography } from '@mui/material';
|
||||||
|
import { formatConstraintValue } from 'utils/formatConstraintValue';
|
||||||
|
import { useStyles } from '../../../ConstraintAccordion.styles';
|
||||||
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
|
import { PlaygroundConstraintSchema } from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
|
const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
|
||||||
|
margin: 'auto 0',
|
||||||
|
[theme.breakpoints.down(710)]: {
|
||||||
|
margin: theme.spacing(1, 0),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface PlaygroundConstraintAccordionViewHeaderSingleValueProps {
|
||||||
|
constraint: PlaygroundConstraintSchema;
|
||||||
|
allowExpand: (shouldExpand: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaygroundSingleValue = ({
|
||||||
|
constraint,
|
||||||
|
allowExpand,
|
||||||
|
}: PlaygroundConstraintAccordionViewHeaderSingleValueProps) => {
|
||||||
|
const { locationSettings } = useLocationSettings();
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
allowExpand(false);
|
||||||
|
}, [allowExpand]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.headerValuesContainerWrapper}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={!Boolean(constraint.result)}
|
||||||
|
show={
|
||||||
|
<Typography variant={'body1'} color={'error'}>
|
||||||
|
does not match any values{' '}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledSingleValueChip
|
||||||
|
label={formatConstraintValue(constraint, locationSettings)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,51 @@
|
|||||||
|
import { Fragment, VFC } from 'react';
|
||||||
|
import {
|
||||||
|
PlaygroundConstraintSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { objectId } from 'utils/objectId';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
import { ConstraintAccordionView } from './ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView';
|
||||||
|
|
||||||
|
interface IConstraintExecutionProps {
|
||||||
|
constraints?: PlaygroundConstraintSchema[];
|
||||||
|
compact: boolean;
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConstraintExecutionWrapper = styled('div')(() => ({
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const ConstraintExecution: VFC<IConstraintExecutionProps> = ({
|
||||||
|
constraints,
|
||||||
|
compact,
|
||||||
|
input,
|
||||||
|
}) => {
|
||||||
|
if (!constraints) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConstraintExecutionWrapper>
|
||||||
|
{constraints?.map((constraint, index) => (
|
||||||
|
<Fragment key={objectId(constraint)}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={index > 0 && constraints?.length > 1}
|
||||||
|
show={<StrategySeparator text="AND" />}
|
||||||
|
/>
|
||||||
|
<ConstraintAccordionView
|
||||||
|
constraint={constraint}
|
||||||
|
playgroundInput={input}
|
||||||
|
maxLength={compact ? 25 : 50}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: 'transparent!important',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</ConstraintExecutionWrapper>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,140 @@
|
|||||||
|
import React, { Fragment, VFC } from 'react';
|
||||||
|
import {
|
||||||
|
parseParameterNumber,
|
||||||
|
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';
|
||||||
|
|
||||||
|
interface ICustomStrategyProps {
|
||||||
|
parameters: { [key: string]: string };
|
||||||
|
strategyName: string;
|
||||||
|
constraints: PlaygroundConstraintSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CustomStrategyParams: VFC<ICustomStrategyProps> = ({
|
||||||
|
strategyName,
|
||||||
|
constraints,
|
||||||
|
parameters,
|
||||||
|
}) => {
|
||||||
|
const { strategies } = useStrategies();
|
||||||
|
const definition = strategies.find(strategyDefinition => {
|
||||||
|
return strategyDefinition.name === strategyName;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!definition?.editable) {
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return <>{renderCustomStrategyParameters()}</>;
|
||||||
|
};
|
@ -0,0 +1,83 @@
|
|||||||
|
import { Chip, Typography, useTheme } from '@mui/material';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { useStyles } from './PlaygroundParametertem.styles';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
import { CancelOutlined } from '@mui/icons-material';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
interface IConstraintItemProps {
|
||||||
|
value: Array<string | number>;
|
||||||
|
text: string;
|
||||||
|
input?: string | number | boolean | 'no value';
|
||||||
|
showReason?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaygroundParameterItem = ({
|
||||||
|
value,
|
||||||
|
text,
|
||||||
|
input,
|
||||||
|
showReason = false,
|
||||||
|
}: IConstraintItemProps) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const color = input === 'no value' ? 'error' : 'neutral';
|
||||||
|
const reason = `value does not match any ${text}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
styles.container,
|
||||||
|
showReason ? styles.disabled : ''
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Typography variant="subtitle1" color={theme.palette[color].main}>
|
||||||
|
{`${input}`}
|
||||||
|
</Typography>
|
||||||
|
<div className={styles.column}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(showReason)}
|
||||||
|
show={
|
||||||
|
<Typography
|
||||||
|
variant="subtitle1"
|
||||||
|
color={theme.palette.error.main}
|
||||||
|
>
|
||||||
|
{reason}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={value.length === 0}
|
||||||
|
show={<p>No {text}s added yet.</p>}
|
||||||
|
elseShow={
|
||||||
|
<div>
|
||||||
|
<p className={styles.paragraph}>
|
||||||
|
{value.length}{' '}
|
||||||
|
{value.length > 1 ? `${text}s` : text} will get
|
||||||
|
access.
|
||||||
|
</p>
|
||||||
|
{value.map((v: string | number) => (
|
||||||
|
<Chip
|
||||||
|
key={v}
|
||||||
|
label={
|
||||||
|
<StringTruncator
|
||||||
|
maxWidth="300"
|
||||||
|
text={v.toString()}
|
||||||
|
maxLength={50}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
className={styles.chip}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(showReason)}
|
||||||
|
show={<CancelOutlined color={'error'} />}
|
||||||
|
elseShow={<div />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,32 @@
|
|||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles()(theme => ({
|
||||||
|
container: {
|
||||||
|
width: '100%',
|
||||||
|
padding: theme.spacing(2, 3),
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
backgroundColor: theme.palette.neutral.light,
|
||||||
|
opacity: '90%',
|
||||||
|
},
|
||||||
|
chip: {
|
||||||
|
margin: '0.25rem',
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
paragraph: {
|
||||||
|
display: 'inline',
|
||||||
|
margin: '0.25rem 0',
|
||||||
|
maxWidth: '95%',
|
||||||
|
textAlign: 'center',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,12 @@
|
|||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles()(theme => ({
|
||||||
|
container: {},
|
||||||
|
link: {
|
||||||
|
textDecoration: 'none',
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
'&:hover': {
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,119 @@
|
|||||||
|
import { VFC } from 'react';
|
||||||
|
import {
|
||||||
|
PlaygroundSegmentSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { ConstraintExecution } from '../ConstraintExecution/ConstraintExecution';
|
||||||
|
import { CancelOutlined, DonutLarge } from '@mui/icons-material';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||||
|
import { useStyles } from './SegmentExecution.styles';
|
||||||
|
import { styled, Typography } from '@mui/material';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
|
interface ISegmentExecutionProps {
|
||||||
|
segments?: PlaygroundSegmentSchema[];
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
|
hasConstraints: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SegmentExecutionLinkWrapper = styled('div')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(2, 3),
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
position: 'relative',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SegmentExecutionHeader = styled('div')(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
'& + &': {
|
||||||
|
margin: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SegmentExecutionWrapper = styled('div')(({ theme }) => ({
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
|
border: `1px solid ${theme.palette.dividerAlternative}`,
|
||||||
|
'& + &': {
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
background: theme.palette.neutral.light,
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SegmentExecutionConstraintWrapper = styled('div')(() => ({
|
||||||
|
padding: '12px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SegmentResultTextWrapper = styled('div')(({ theme }) => ({
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: '12px',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const SegmentExecution: VFC<ISegmentExecutionProps> = ({
|
||||||
|
segments,
|
||||||
|
input,
|
||||||
|
hasConstraints,
|
||||||
|
}) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
|
||||||
|
if (!segments) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{segments.map((segment, index) => (
|
||||||
|
<SegmentExecutionWrapper key={segment.id}>
|
||||||
|
<SegmentExecutionHeader>
|
||||||
|
<SegmentExecutionLinkWrapper>
|
||||||
|
<DonutLarge color="secondary" sx={{ mr: 1 }} />{' '}
|
||||||
|
Segment:{' '}
|
||||||
|
<Link
|
||||||
|
to={`/segments/edit/${segment.id}`}
|
||||||
|
className={styles.link}
|
||||||
|
>
|
||||||
|
{segment.name}
|
||||||
|
</Link>
|
||||||
|
</SegmentExecutionLinkWrapper>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={!Boolean(segment.result)}
|
||||||
|
show={
|
||||||
|
<SegmentResultTextWrapper>
|
||||||
|
<Typography
|
||||||
|
variant={'subtitle2'}
|
||||||
|
sx={{ pt: 0.25 }}
|
||||||
|
>
|
||||||
|
segment is false
|
||||||
|
</Typography>
|
||||||
|
<span>
|
||||||
|
<CancelOutlined />
|
||||||
|
</span>
|
||||||
|
</SegmentResultTextWrapper>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SegmentExecutionHeader>
|
||||||
|
<SegmentExecutionConstraintWrapper>
|
||||||
|
<ConstraintExecution
|
||||||
|
constraints={segment.constraints}
|
||||||
|
input={input}
|
||||||
|
compact
|
||||||
|
/>
|
||||||
|
</SegmentExecutionConstraintWrapper>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
index === segments?.length - 1 && hasConstraints
|
||||||
|
}
|
||||||
|
show={<StrategySeparator text="AND" />}
|
||||||
|
/>
|
||||||
|
</SegmentExecutionWrapper>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,19 @@
|
|||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles()(theme => ({
|
||||||
|
valueContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '1ch',
|
||||||
|
},
|
||||||
|
valueSeparator: {
|
||||||
|
color: theme.palette.grey[700],
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
width: 'auto',
|
||||||
|
height: 'auto',
|
||||||
|
padding: theme.spacing(2, 3),
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,112 @@
|
|||||||
|
import { VFC } from 'react';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||||
|
import { Box, Chip, styled } from '@mui/material';
|
||||||
|
import { useStyles } from './StrategyExecution.styles';
|
||||||
|
import {
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
PlaygroundStrategySchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { ConstraintExecution } from './ConstraintExecution/ConstraintExecution';
|
||||||
|
import { SegmentExecution } from './SegmentExecution/SegmentExecution';
|
||||||
|
import { PlaygroundResultStrategyExecutionParameters } from './StrategyExecutionParameters/StrategyExecutionParameters';
|
||||||
|
import { CustomStrategyParams } from './CustomStrategyParams/CustomStrategyParams';
|
||||||
|
|
||||||
|
interface IStrategyExecutionProps {
|
||||||
|
strategyResult: PlaygroundStrategySchema;
|
||||||
|
percentageFill?: string;
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}) => {
|
||||||
|
const { name, constraints, segments, parameters } = strategyResult;
|
||||||
|
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
|
||||||
|
const hasConstraints = Boolean(constraints && constraints?.length > 0);
|
||||||
|
const hasParameters = Object.keys(parameters).length === 0;
|
||||||
|
|
||||||
|
if (!parameters) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledStrategyExecutionWrapper>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
Boolean(uiConfig.flags.SE) &&
|
||||||
|
Boolean(segments && segments.length > 0)
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<SegmentExecution
|
||||||
|
segments={segments}
|
||||||
|
hasConstraints={hasConstraints}
|
||||||
|
input={input}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(constraints && constraints.length > 0)}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<ConstraintExecution
|
||||||
|
constraints={constraints}
|
||||||
|
compact={true}
|
||||||
|
input={input}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(
|
||||||
|
constraints &&
|
||||||
|
constraints.length > 0 &&
|
||||||
|
!hasParameters
|
||||||
|
)}
|
||||||
|
show={<StrategySeparator text="AND" />}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={name === 'default'}
|
||||||
|
show={
|
||||||
|
<Box sx={{ width: '100%' }} className={styles.summary}>
|
||||||
|
The standard strategyResult is{' '}
|
||||||
|
<Chip
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
color="success"
|
||||||
|
label="ON"
|
||||||
|
/>{' '}
|
||||||
|
for all users.
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledParamWrapper>
|
||||||
|
<PlaygroundResultStrategyExecutionParameters
|
||||||
|
parameters={parameters}
|
||||||
|
constraints={constraints}
|
||||||
|
input={input}
|
||||||
|
/>
|
||||||
|
<StyledParamWrapper sx={{ pt: 2 }}>
|
||||||
|
<CustomStrategyParams
|
||||||
|
strategyName={strategyResult.name}
|
||||||
|
parameters={parameters}
|
||||||
|
constraints={constraints}
|
||||||
|
/>
|
||||||
|
</StyledParamWrapper>
|
||||||
|
</StyledParamWrapper>
|
||||||
|
</StyledStrategyExecutionWrapper>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,126 @@
|
|||||||
|
import {
|
||||||
|
parseParameterNumber,
|
||||||
|
parseParameterStrings,
|
||||||
|
} from 'utils/parseParameter';
|
||||||
|
import { Box, Chip } from '@mui/material';
|
||||||
|
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
||||||
|
import { PlaygroundParameterItem } from '../PlaygroundParameterItem/PlaygroundParameterItem';
|
||||||
|
import React from 'react';
|
||||||
|
import { useStyles } from '../StrategyExecution.styles';
|
||||||
|
import {
|
||||||
|
PlaygroundConstraintSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { getMappedParam } from '../helpers';
|
||||||
|
|
||||||
|
export interface PlaygroundResultStrategyExecutionParametersProps {
|
||||||
|
parameters: { [key: string]: string };
|
||||||
|
constraints: PlaygroundConstraintSchema[];
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaygroundResultStrategyExecutionParameters = ({
|
||||||
|
parameters,
|
||||||
|
constraints,
|
||||||
|
input,
|
||||||
|
}: PlaygroundResultStrategyExecutionParametersProps) => {
|
||||||
|
const { classes: styles } = useStyles();
|
||||||
|
const renderParameters = () => {
|
||||||
|
return Object.keys(parameters).map(key => {
|
||||||
|
switch (key) {
|
||||||
|
case 'rollout':
|
||||||
|
case 'Rollout':
|
||||||
|
const percentage = parseParameterNumber(parameters[key]);
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className={styles.summary}
|
||||||
|
key={key}
|
||||||
|
sx={{ display: 'flex', alignItems: 'center' }}
|
||||||
|
>
|
||||||
|
<Box sx={{ mr: '1rem' }}>
|
||||||
|
<PercentageCircle
|
||||||
|
percentage={percentage}
|
||||||
|
size="2rem"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<div>
|
||||||
|
<Chip
|
||||||
|
color="success"
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
label={`${percentage}%`}
|
||||||
|
/>{' '}
|
||||||
|
of your base{' '}
|
||||||
|
{constraints.length > 0
|
||||||
|
? 'who match constraints'
|
||||||
|
: ''}{' '}
|
||||||
|
is included.
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
case 'userIds':
|
||||||
|
case 'UserIds':
|
||||||
|
const users = parseParameterStrings(parameters[key]);
|
||||||
|
return (
|
||||||
|
<PlaygroundParameterItem
|
||||||
|
key={key}
|
||||||
|
value={users}
|
||||||
|
text="user"
|
||||||
|
input={
|
||||||
|
Boolean(input?.context?.[getMappedParam(key)])
|
||||||
|
? input?.context?.[getMappedParam(key)]
|
||||||
|
: 'no value'
|
||||||
|
}
|
||||||
|
showReason={
|
||||||
|
Boolean(input?.context?.[getMappedParam(key)])
|
||||||
|
? !users.includes(
|
||||||
|
input?.context?.[getMappedParam(key)]
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'hostNames':
|
||||||
|
case 'HostNames':
|
||||||
|
const hosts = parseParameterStrings(parameters[key]);
|
||||||
|
return (
|
||||||
|
<PlaygroundParameterItem
|
||||||
|
key={key}
|
||||||
|
value={hosts}
|
||||||
|
text={'host'}
|
||||||
|
input={'no value'}
|
||||||
|
showReason={undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'IPs':
|
||||||
|
const IPs = parseParameterStrings(parameters[key]);
|
||||||
|
return (
|
||||||
|
<PlaygroundParameterItem
|
||||||
|
key={key}
|
||||||
|
value={IPs}
|
||||||
|
text={'IP'}
|
||||||
|
input={
|
||||||
|
Boolean(input?.context?.[getMappedParam(key)])
|
||||||
|
? input?.context?.[getMappedParam(key)]
|
||||||
|
: 'no value'
|
||||||
|
}
|
||||||
|
showReason={
|
||||||
|
Boolean(input?.context?.[getMappedParam(key)])
|
||||||
|
? !IPs.includes(
|
||||||
|
input?.context?.[getMappedParam(key)]
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 'stickiness':
|
||||||
|
case 'groupId':
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return <>{renderParameters()}</>;
|
||||||
|
};
|
@ -0,0 +1,10 @@
|
|||||||
|
export const getMappedParam = (key: string) => {
|
||||||
|
switch (key.toUpperCase()) {
|
||||||
|
case 'USERIDS':
|
||||||
|
return 'userId';
|
||||||
|
case 'IPS':
|
||||||
|
return 'remoteAddress';
|
||||||
|
default:
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,92 @@
|
|||||||
|
import { Fragment } from 'react';
|
||||||
|
import { Alert, Box, styled, Typography } from '@mui/material';
|
||||||
|
import {
|
||||||
|
PlaygroundFeatureSchema,
|
||||||
|
PlaygroundStrategySchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { FeatureStrategyItem } from './StrategyItem/FeatureStrategyItem';
|
||||||
|
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||||
|
|
||||||
|
const StyledAlertWrapper = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
padding: `0, 4px`,
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
|
border: `1px solid ${theme.palette.info.border}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledListWrapper = styled('div')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(1, 0.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledAlert = styled(Alert)(() => ({
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface PlaygroundResultStrategyListProps {
|
||||||
|
strategies: PlaygroundStrategySchema[];
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaygroundResultStrategyLists = ({
|
||||||
|
strategies,
|
||||||
|
input,
|
||||||
|
}: PlaygroundResultStrategyListProps) => (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={strategies.length > 0}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<Typography
|
||||||
|
variant={'subtitle1'}
|
||||||
|
sx={{ mt: 2, ml: 1, mb: 2, color: 'text.secondary' }}
|
||||||
|
>{`Strategies (${strategies.length})`}</Typography>
|
||||||
|
<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 WrappedPlaygroundResultStrategyListProps
|
||||||
|
extends PlaygroundResultStrategyListProps {
|
||||||
|
feature: PlaygroundFeatureSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WrappedPlaygroundResultStrategyList = ({
|
||||||
|
strategies,
|
||||||
|
feature,
|
||||||
|
input,
|
||||||
|
}: WrappedPlaygroundResultStrategyListProps) => {
|
||||||
|
return (
|
||||||
|
<StyledAlertWrapper sx={{ pb: 1 }}>
|
||||||
|
<StyledAlert severity={'info'} color={'info'}>
|
||||||
|
If environment would be enabled then this feature would be{' '}
|
||||||
|
{feature.strategies.result ? 'TRUE' : 'FALSE'} and the
|
||||||
|
strategies would evaluate like this:{' '}
|
||||||
|
</StyledAlert>
|
||||||
|
<StyledListWrapper>
|
||||||
|
<PlaygroundResultStrategyLists
|
||||||
|
strategies={strategies}
|
||||||
|
input={input}
|
||||||
|
/>
|
||||||
|
</StyledListWrapper>
|
||||||
|
</StyledAlertWrapper>
|
||||||
|
);
|
||||||
|
};
|
@ -1,40 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { colors } from 'themes/colors';
|
import { Box, styled } from '@mui/material';
|
||||||
import { ReactComponent as FeatureEnabledIcon } from 'assets/icons/isenabled-true.svg';
|
import { PlaygroundResultChip } from '../PlaygroundResultChip/PlaygroundResultChip';
|
||||||
import { ReactComponent as FeatureDisabledIcon } from 'assets/icons/isenabled-false.svg';
|
import { PlaygroundFeatureSchema } from '../../interfaces/playground.model';
|
||||||
import { Box, Chip, styled, useTheme } from '@mui/material';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
|
|
||||||
interface IFeatureStatusCellProps {
|
interface IFeatureStatusCellProps {
|
||||||
enabled: boolean;
|
feature: PlaygroundFeatureSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledFalseChip = styled(Chip)(({ theme }) => ({
|
|
||||||
width: 80,
|
|
||||||
borderRadius: '5px',
|
|
||||||
border: `1px solid ${theme.palette.error.main}`,
|
|
||||||
backgroundColor: colors.red['200'],
|
|
||||||
['& .MuiChip-label']: {
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
},
|
|
||||||
['& .MuiChip-icon']: {
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledTrueChip = styled(Chip)(({ theme }) => ({
|
|
||||||
width: 80,
|
|
||||||
borderRadius: '5px',
|
|
||||||
border: `1px solid ${theme.palette.success.main}`,
|
|
||||||
backgroundColor: colors.green['100'],
|
|
||||||
['& .MuiChip-label']: {
|
|
||||||
color: theme.palette.success.main,
|
|
||||||
},
|
|
||||||
['& .MuiChip-icon']: {
|
|
||||||
color: theme.palette.success.main,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledCellBox = styled(Box)(({ theme }) => ({
|
const StyledCellBox = styled(Box)(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@ -45,35 +17,25 @@ const StyledChipWrapper = styled(Box)(() => ({
|
|||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const FeatureStatusCell = ({ enabled }: IFeatureStatusCellProps) => {
|
export const FeatureStatusCell = ({ feature }: IFeatureStatusCellProps) => {
|
||||||
const theme = useTheme();
|
const enabled = feature.isEnabled
|
||||||
const icon = (
|
? true
|
||||||
<ConditionallyRender
|
: feature.strategies.result === false
|
||||||
condition={enabled}
|
? false
|
||||||
show={
|
: 'unknown';
|
||||||
<FeatureEnabledIcon
|
const label = feature.isEnabled
|
||||||
color={theme.palette.success.main}
|
? 'True'
|
||||||
strokeWidth="0.25"
|
: feature.strategies.result === false
|
||||||
/>
|
? 'False'
|
||||||
}
|
: 'Unknown';
|
||||||
elseShow={
|
|
||||||
<FeatureDisabledIcon
|
|
||||||
color={theme.palette.error.main}
|
|
||||||
strokeWidth="0.25"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const label = enabled ? 'True' : 'False';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledCellBox>
|
<StyledCellBox>
|
||||||
<StyledChipWrapper data-loading>
|
<StyledChipWrapper data-loading>
|
||||||
<ConditionallyRender
|
<PlaygroundResultChip
|
||||||
condition={enabled}
|
enabled={enabled}
|
||||||
show={<StyledTrueChip icon={icon} label={label} />}
|
label={label}
|
||||||
elseShow={<StyledFalseChip icon={icon} label={label} />}
|
showIcon={enabled !== 'unknown'}
|
||||||
|
size={'medium'}
|
||||||
/>
|
/>
|
||||||
</StyledChipWrapper>
|
</StyledChipWrapper>
|
||||||
</StyledCellBox>
|
</StyledCellBox>
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
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 { WarningOutlined } from '@mui/icons-material';
|
||||||
|
|
||||||
|
interface IResultChipProps {
|
||||||
|
enabled: boolean | 'unevaluated' | 'unknown';
|
||||||
|
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 StyledFalseChip = styled(StyledChip)(({ theme }) => ({
|
||||||
|
border: `1px solid ${theme.palette.error.main}`,
|
||||||
|
backgroundColor: colors.red['200'],
|
||||||
|
['& .MuiChip-label']: {
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
},
|
||||||
|
['& .MuiChip-icon']: {
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const StyledTrueChip = styled(StyledChip)(({ theme }) => ({
|
||||||
|
border: `1px solid ${theme.palette.success.main}`,
|
||||||
|
backgroundColor: colors.green['100'],
|
||||||
|
['& .MuiChip-label']: {
|
||||||
|
color: theme.palette.success.main,
|
||||||
|
},
|
||||||
|
['& .MuiChip-icon']: {
|
||||||
|
color: theme.palette.success.main,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const StyledUnknownChip = styled(StyledChip)(({ theme }) => ({
|
||||||
|
border: `1px solid ${theme.palette.warning.main}`,
|
||||||
|
backgroundColor: colors.orange['100'],
|
||||||
|
['& .MuiChip-label']: {
|
||||||
|
color: theme.palette.warning.main,
|
||||||
|
},
|
||||||
|
['& .MuiChip-icon']: {
|
||||||
|
color: theme.palette.warning.main,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const PlaygroundResultChip = ({
|
||||||
|
enabled,
|
||||||
|
label,
|
||||||
|
showIcon = true,
|
||||||
|
size = 'default',
|
||||||
|
}: IResultChipProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const icon = (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={enabled === 'unknown' || enabled === 'unevaluated'}
|
||||||
|
show={<WarningOutlined color={'warning'} fontSize="inherit" />}
|
||||||
|
elseShow={
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={typeof enabled === 'boolean' && Boolean(enabled)}
|
||||||
|
show={
|
||||||
|
<FeatureEnabledIcon
|
||||||
|
color={theme.palette.success.main}
|
||||||
|
strokeWidth="0.25"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<FeatureDisabledIcon
|
||||||
|
color={theme.palette.error.main}
|
||||||
|
strokeWidth="0.25"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
let chipWidth = 60;
|
||||||
|
if (size === 'medium') chipWidth = 72;
|
||||||
|
if (size === 'large') chipWidth = 100;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={enabled === 'unknown' || enabled === 'unevaluated'}
|
||||||
|
show={
|
||||||
|
<StyledUnknownChip
|
||||||
|
icon={showIcon ? icon : undefined}
|
||||||
|
label={label}
|
||||||
|
width={chipWidth}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={typeof enabled === 'boolean' && Boolean(enabled)}
|
||||||
|
show={
|
||||||
|
<StyledTrueChip
|
||||||
|
icon={showIcon ? icon : undefined}
|
||||||
|
label={label}
|
||||||
|
width={chipWidth}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<StyledFalseChip
|
||||||
|
icon={showIcon ? icon : undefined}
|
||||||
|
label={label}
|
||||||
|
width={chipWidth}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -19,10 +19,14 @@ import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
|||||||
import { useSearch } from 'hooks/useSearch';
|
import { useSearch } from 'hooks/useSearch';
|
||||||
import { createLocalStorage } from 'utils/createLocalStorage';
|
import { createLocalStorage } from 'utils/createLocalStorage';
|
||||||
import { FeatureStatusCell } from './FeatureStatusCell/FeatureStatusCell';
|
import { FeatureStatusCell } from './FeatureStatusCell/FeatureStatusCell';
|
||||||
import { PlaygroundFeatureSchema } from 'hooks/api/actions/usePlayground/playground.model';
|
import {
|
||||||
|
PlaygroundFeatureSchema,
|
||||||
|
PlaygroundRequestSchema,
|
||||||
|
} from 'component/playground/Playground/interfaces/playground.model';
|
||||||
import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
|
import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import useLoading from 'hooks/useLoading';
|
import useLoading from 'hooks/useLoading';
|
||||||
import { VariantCell } from './VariantCell/VariantCell';
|
import { VariantCell } from './VariantCell/VariantCell';
|
||||||
|
import { FeatureResultInfoPopoverCell } from './FeatureResultInfoPopoverCell/FeatureResultInfoPopoverCell';
|
||||||
|
|
||||||
const defaultSort: SortingRule<string> = { id: 'name' };
|
const defaultSort: SortingRule<string> = { id: 'name' };
|
||||||
const { value, setValue } = createLocalStorage(
|
const { value, setValue } = createLocalStorage(
|
||||||
@ -32,11 +36,13 @@ const { value, setValue } = createLocalStorage(
|
|||||||
|
|
||||||
interface IPlaygroundResultsTableProps {
|
interface IPlaygroundResultsTableProps {
|
||||||
features?: PlaygroundFeatureSchema[];
|
features?: PlaygroundFeatureSchema[];
|
||||||
|
input?: PlaygroundRequestSchema;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PlaygroundResultsTable = ({
|
export const PlaygroundResultsTable = ({
|
||||||
features,
|
features,
|
||||||
|
input,
|
||||||
loading,
|
loading,
|
||||||
}: IPlaygroundResultsTableProps) => {
|
}: IPlaygroundResultsTableProps) => {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -48,6 +54,77 @@ export const PlaygroundResultsTable = ({
|
|||||||
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
|
const COLUMNS = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
Header: 'Name',
|
||||||
|
accessor: 'name',
|
||||||
|
searchable: true,
|
||||||
|
minWidth: 160,
|
||||||
|
Cell: ({ value, row: { original } }: any) => (
|
||||||
|
<LinkCell
|
||||||
|
title={value}
|
||||||
|
to={`/projects/${original?.projectId}/features/${value}`}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Project ID',
|
||||||
|
accessor: 'projectId',
|
||||||
|
sortType: 'alphanumeric',
|
||||||
|
filterName: 'projectId',
|
||||||
|
searchable: true,
|
||||||
|
maxWidth: 170,
|
||||||
|
Cell: ({ value }: any) => (
|
||||||
|
<LinkCell title={value} to={`/projects/${value}`} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Variant',
|
||||||
|
id: 'variant',
|
||||||
|
accessor: 'variant.name',
|
||||||
|
sortType: 'alphanumeric',
|
||||||
|
filterName: 'variant',
|
||||||
|
searchable: true,
|
||||||
|
width: 200,
|
||||||
|
Cell: ({
|
||||||
|
value,
|
||||||
|
row: {
|
||||||
|
original: { variant, feature, variants, isEnabled },
|
||||||
|
},
|
||||||
|
}: any) => (
|
||||||
|
<VariantCell
|
||||||
|
variant={variant?.enabled ? value : ''}
|
||||||
|
variants={variants}
|
||||||
|
feature={feature}
|
||||||
|
isEnabled={isEnabled}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'isEnabled',
|
||||||
|
accessor: 'isEnabled',
|
||||||
|
filterName: 'isEnabled',
|
||||||
|
filterParsing: (value: boolean) => (value ? 'true' : 'false'),
|
||||||
|
Cell: ({ row }: any) => (
|
||||||
|
<FeatureStatusCell feature={row.original} />
|
||||||
|
),
|
||||||
|
sortType: 'boolean',
|
||||||
|
sortInverted: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: '',
|
||||||
|
id: 'info',
|
||||||
|
Cell: ({ row }: any) => (
|
||||||
|
<FeatureResultInfoPopoverCell
|
||||||
|
feature={row.original}
|
||||||
|
input={input}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [input]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: searchedData,
|
data: searchedData,
|
||||||
getSearchText,
|
getSearchText,
|
||||||
@ -235,60 +312,3 @@ export const PlaygroundResultsTable = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLUMNS = [
|
|
||||||
{
|
|
||||||
Header: 'Name',
|
|
||||||
accessor: 'name',
|
|
||||||
searchable: true,
|
|
||||||
minWidth: 160,
|
|
||||||
Cell: ({ value, row: { original } }: any) => (
|
|
||||||
<LinkCell
|
|
||||||
title={value}
|
|
||||||
to={`/projects/${original?.projectId}/features/${value}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Project ID',
|
|
||||||
accessor: 'projectId',
|
|
||||||
sortType: 'alphanumeric',
|
|
||||||
filterName: 'projectId',
|
|
||||||
searchable: true,
|
|
||||||
maxWidth: 170,
|
|
||||||
Cell: ({ value }: any) => (
|
|
||||||
<LinkCell title={value} to={`/projects/${value}`} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Variant',
|
|
||||||
id: 'variant',
|
|
||||||
accessor: 'variant.name',
|
|
||||||
sortType: 'alphanumeric',
|
|
||||||
filterName: 'variant',
|
|
||||||
searchable: true,
|
|
||||||
width: 200,
|
|
||||||
Cell: ({
|
|
||||||
value,
|
|
||||||
row: {
|
|
||||||
original: { variant, feature, variants, isEnabled },
|
|
||||||
},
|
|
||||||
}: any) => (
|
|
||||||
<VariantCell
|
|
||||||
variant={variant?.enabled ? value : ''}
|
|
||||||
variants={variants}
|
|
||||||
feature={feature}
|
|
||||||
isEnabled={isEnabled}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'isEnabled',
|
|
||||||
accessor: 'isEnabled',
|
|
||||||
filterName: 'isEnabled',
|
|
||||||
filterParsing: (value: boolean) => (value ? 'true' : 'false'),
|
|
||||||
Cell: ({ value }: any) => <FeatureStatusCell enabled={value} />,
|
|
||||||
sortType: 'boolean',
|
|
||||||
sortInverted: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
@ -0,0 +1,321 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* 09/08/2022
|
||||||
|
* This was copied from the openapi-generator generated files and slightly modified
|
||||||
|
* because of malformed generation of `anyOf`, `oneOf`
|
||||||
|
*
|
||||||
|
* https://github.com/OpenAPITools/openapi-generator/issues/12256
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { VariantSchema } from 'openapi';
|
||||||
|
import { Operator } from 'constants/operators';
|
||||||
|
|
||||||
|
export interface PlaygroundConstraintSchema {
|
||||||
|
/**
|
||||||
|
* The name of the context field that this constraint should apply to.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundConstraintSchema
|
||||||
|
*/
|
||||||
|
contextName: string;
|
||||||
|
/**
|
||||||
|
* The operator to use when evaluating this constraint. For more information about the various operators, refer to [the strategy constraint operator documentation](https://docs.getunleash.io/advanced/strategy_constraints#strategy-constraint-operators).
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundConstraintSchema
|
||||||
|
*/
|
||||||
|
operator: Operator;
|
||||||
|
/**
|
||||||
|
* Whether the operator should be case-sensitive or not. Defaults to `false` (being case-sensitive).
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof PlaygroundConstraintSchema
|
||||||
|
*/
|
||||||
|
caseInsensitive?: boolean;
|
||||||
|
/**
|
||||||
|
* Whether the result should be negated or not. If `true`, will turn a `true` result into a `false` result and vice versa.
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof PlaygroundConstraintSchema
|
||||||
|
*/
|
||||||
|
inverted?: boolean;
|
||||||
|
/**
|
||||||
|
* The context values that should be used for constraint evaluation. Use this property instead of `value` for properties that accept multiple values.
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @memberof PlaygroundConstraintSchema
|
||||||
|
*/
|
||||||
|
values?: Array<string>;
|
||||||
|
/**
|
||||||
|
* The context value that should be used for constraint evaluation. Use this property instead of `values` for properties that only accept single values.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundConstraintSchema
|
||||||
|
*/
|
||||||
|
value?: string;
|
||||||
|
/**
|
||||||
|
* Whether this was evaluated as true or false.
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof PlaygroundConstraintSchema
|
||||||
|
*/
|
||||||
|
result: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaygroundFeatureSchema {
|
||||||
|
/**
|
||||||
|
* The feature's name.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundFeatureSchema
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The ID of the project that contains this feature.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundFeatureSchema
|
||||||
|
*/
|
||||||
|
projectId: string;
|
||||||
|
/**
|
||||||
|
* The strategies that apply to this feature.
|
||||||
|
* @type {Array<PlaygroundStrategySchema>}
|
||||||
|
* @memberof PlaygroundFeatureSchema
|
||||||
|
*/
|
||||||
|
strategies: PlaygroundStrategyResultSchema;
|
||||||
|
/**
|
||||||
|
* Whether the feature is active and would be evaluated in the provided environment in a normal SDK context.
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof PlaygroundFeatureSchema
|
||||||
|
*/
|
||||||
|
isEnabledInCurrentEnvironment: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean | 'unevaluated'}
|
||||||
|
* @memberof PlaygroundFeatureSchema
|
||||||
|
*/
|
||||||
|
isEnabled: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {PlaygroundFeatureSchemaVariant}
|
||||||
|
* @memberof PlaygroundFeatureSchema
|
||||||
|
*/
|
||||||
|
variant: PlaygroundFeatureSchemaVariant | null;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {Array<VariantSchema>}
|
||||||
|
* @memberof PlaygroundFeatureSchema
|
||||||
|
*/
|
||||||
|
variants: Array<VariantSchema>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaygroundFeatureSchemaVariant {
|
||||||
|
/**
|
||||||
|
* The variant's name. If there is no variant or if the toggle is disabled, this will be `disabled`
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundFeatureSchemaVariant
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Whether the variant is enabled or not. If the feature is disabled or if it doesn't have variants, this property will be `false`
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof PlaygroundFeatureSchemaVariant
|
||||||
|
*/
|
||||||
|
enabled: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {PlaygroundFeatureSchemaVariantPayload}
|
||||||
|
* @memberof PlaygroundFeatureSchemaVariant
|
||||||
|
*/
|
||||||
|
payload?: PlaygroundFeatureSchemaVariantPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaygroundFeatureSchemaVariantPayload {
|
||||||
|
/**
|
||||||
|
* The format of the payload.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundFeatureSchemaVariantPayload
|
||||||
|
*/
|
||||||
|
type: PlaygroundFeatureSchemaVariantPayloadTypeEnum;
|
||||||
|
/**
|
||||||
|
* The payload value stringified.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundFeatureSchemaVariantPayload
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const playgroundFeatureSchemaVariantPayloadTypeEnum = {
|
||||||
|
Json: 'json',
|
||||||
|
Csv: 'csv',
|
||||||
|
String: 'string',
|
||||||
|
} as const;
|
||||||
|
export type PlaygroundFeatureSchemaVariantPayloadTypeEnum =
|
||||||
|
typeof playgroundFeatureSchemaVariantPayloadTypeEnum[keyof typeof playgroundFeatureSchemaVariantPayloadTypeEnum];
|
||||||
|
|
||||||
|
export interface PlaygroundRequestSchema {
|
||||||
|
/**
|
||||||
|
* The environment to evaluate toggles in.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundRequestSchema
|
||||||
|
*/
|
||||||
|
environment: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {PlaygroundRequestSchemaProjects}
|
||||||
|
* @memberof PlaygroundRequestSchema
|
||||||
|
*/
|
||||||
|
projects?: PlaygroundRequestSchemaProjects;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {SdkContextSchema}
|
||||||
|
* @memberof PlaygroundRequestSchema
|
||||||
|
*/
|
||||||
|
context: SdkContextSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlaygroundRequestSchemaProjects = Array<string> | string;
|
||||||
|
|
||||||
|
export interface PlaygroundResponseSchema {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {PlaygroundRequestSchema}
|
||||||
|
* @memberof PlaygroundResponseSchema
|
||||||
|
*/
|
||||||
|
input: PlaygroundRequestSchema;
|
||||||
|
/**
|
||||||
|
* The list of features that have been evaluated.
|
||||||
|
* @type {Array<PlaygroundFeatureSchema>}
|
||||||
|
* @memberof PlaygroundResponseSchema
|
||||||
|
*/
|
||||||
|
features: Array<PlaygroundFeatureSchema>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaygroundSegmentSchema {
|
||||||
|
/**
|
||||||
|
* The segment's id.
|
||||||
|
* @type {number}
|
||||||
|
* @memberof PlaygroundSegmentSchema
|
||||||
|
*/
|
||||||
|
id: number;
|
||||||
|
/**
|
||||||
|
* The name of the segment.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundSegmentSchema
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Whether this was evaluated as true or false.
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof PlaygroundSegmentSchema
|
||||||
|
*/
|
||||||
|
result: boolean;
|
||||||
|
/**
|
||||||
|
* The list of constraints in this segment.
|
||||||
|
* @type {Array<PlaygroundConstraintSchema>}
|
||||||
|
* @memberof PlaygroundSegmentSchema
|
||||||
|
*/
|
||||||
|
constraints: Array<PlaygroundConstraintSchema>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaygroundStrategyResultSchema {
|
||||||
|
result: boolean | 'unknown';
|
||||||
|
data?: Array<PlaygroundStrategySchema>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaygroundStrategySchema {
|
||||||
|
/**
|
||||||
|
* The strategy's name.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundStrategySchema
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The strategy's id.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundStrategySchema
|
||||||
|
*/
|
||||||
|
id?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {PlaygroundStrategySchemaResult}
|
||||||
|
* @memberof PlaygroundStrategySchema
|
||||||
|
*/
|
||||||
|
result: PlaygroundStrategySchemaResult;
|
||||||
|
/**
|
||||||
|
* The strategy's segments and their evaluation results.
|
||||||
|
* @type {Array<PlaygroundSegmentSchema>}
|
||||||
|
* @memberof PlaygroundStrategySchema
|
||||||
|
*/
|
||||||
|
segments: Array<PlaygroundSegmentSchema>;
|
||||||
|
/**
|
||||||
|
* The strategy's constraints and their evaluation results.
|
||||||
|
* @type {Array<PlaygroundConstraintSchema>}
|
||||||
|
* @memberof PlaygroundStrategySchema
|
||||||
|
*/
|
||||||
|
constraints: Array<PlaygroundConstraintSchema>;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {{ [key: string]: string; }}
|
||||||
|
* @memberof PlaygroundStrategySchema
|
||||||
|
*/
|
||||||
|
parameters: { [key: string]: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PlaygroundStrategyResultEvaluationStatusEnum {
|
||||||
|
complete = 'complete',
|
||||||
|
incomplete = 'incomplete',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaygroundStrategySchemaResult {
|
||||||
|
/**
|
||||||
|
* Signals that this strategy was evaluated successfully.
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PlaygroundStrategySchemaResult
|
||||||
|
*/
|
||||||
|
evaluationStatus?: PlaygroundStrategyResultEvaluationStatusEnum;
|
||||||
|
/**
|
||||||
|
* Whether this strategy evaluates to true or not.
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof PlaygroundStrategySchemaResult
|
||||||
|
*/
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SdkContextSchema {
|
||||||
|
[key: string]: string | any;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SdkContextSchema
|
||||||
|
*/
|
||||||
|
appName: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {Date}
|
||||||
|
* @memberof SdkContextSchema
|
||||||
|
*/
|
||||||
|
currentTime?: Date;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SdkContextSchema
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
environment?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {{ [key: string]: string; }}
|
||||||
|
* @memberof SdkContextSchema
|
||||||
|
*/
|
||||||
|
properties?: { [key: string]: string };
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SdkContextSchema
|
||||||
|
*/
|
||||||
|
remoteAddress?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SdkContextSchema
|
||||||
|
*/
|
||||||
|
sessionId?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SdkContextSchema
|
||||||
|
*/
|
||||||
|
userId?: string;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { PlaygroundResponseSchema } from 'hooks/api/actions/usePlayground/playground.model';
|
import { PlaygroundResponseSchema } from 'component/playground/Playground/interfaces/playground.model';
|
||||||
import { IEnvironment } from 'interfaces/environments';
|
import { IEnvironment } from 'interfaces/environments';
|
||||||
|
|
||||||
export const resolveProjects = (
|
export const resolveProjects = (
|
||||||
|
@ -1,250 +0,0 @@
|
|||||||
// TODO: replace with auto-generated openapi code
|
|
||||||
|
|
||||||
export enum PlaygroundFeatureSchemaVariantPayloadTypeEnum {
|
|
||||||
Json = 'json',
|
|
||||||
Csv = 'csv',
|
|
||||||
String = 'string',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaygroundFeatureSchemaVariantPayload {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariantPayload
|
|
||||||
*/
|
|
||||||
type: PlaygroundFeatureSchemaVariantPayloadTypeEnum;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariantPayload
|
|
||||||
*/
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaygroundFeatureSchemaVariant {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariant
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariant
|
|
||||||
*/
|
|
||||||
enabled: boolean;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {PlaygroundFeatureSchemaVariantPayload}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariant
|
|
||||||
*/
|
|
||||||
payload?: PlaygroundFeatureSchemaVariantPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaygroundFeatureSchema {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchema
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchema
|
|
||||||
*/
|
|
||||||
projectId: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof PlaygroundFeatureSchema
|
|
||||||
*/
|
|
||||||
isEnabled: boolean;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {PlaygroundFeatureSchemaVariant}
|
|
||||||
* @memberof PlaygroundFeatureSchema
|
|
||||||
*/
|
|
||||||
variant: PlaygroundFeatureSchemaVariant | null;
|
|
||||||
}
|
|
||||||
export interface PlaygroundResponseSchema {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {PlaygroundRequestSchema}
|
|
||||||
* @memberof PlaygroundResponseSchema
|
|
||||||
*/
|
|
||||||
input: PlaygroundRequestSchema;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<PlaygroundFeatureSchema>}
|
|
||||||
* @memberof PlaygroundResponseSchema
|
|
||||||
*/
|
|
||||||
features: Array<PlaygroundFeatureSchema>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaygroundRequestSchema {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundRequestSchema
|
|
||||||
*/
|
|
||||||
environment: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {PlaygroundRequestSchemaProjects}
|
|
||||||
* @memberof PlaygroundRequestSchema
|
|
||||||
*/
|
|
||||||
projects?: Array<string> | string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {SdkContextSchema}
|
|
||||||
* @memberof PlaygroundRequestSchema
|
|
||||||
*/
|
|
||||||
context: SdkContextSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaygroundFeatureSchemaVariantPayload {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariantPayload
|
|
||||||
*/
|
|
||||||
type: PlaygroundFeatureSchemaVariantPayloadTypeEnum;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariantPayload
|
|
||||||
*/
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaygroundFeatureSchemaVariant {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariant
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariant
|
|
||||||
*/
|
|
||||||
enabled: boolean;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {PlaygroundFeatureSchemaVariantPayload}
|
|
||||||
* @memberof PlaygroundFeatureSchemaVariant
|
|
||||||
*/
|
|
||||||
payload?: PlaygroundFeatureSchemaVariantPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaygroundFeatureSchema {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchema
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundFeatureSchema
|
|
||||||
*/
|
|
||||||
projectId: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof PlaygroundFeatureSchema
|
|
||||||
*/
|
|
||||||
isEnabled: boolean;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {PlaygroundFeatureSchemaVariant}
|
|
||||||
* @memberof PlaygroundFeatureSchema
|
|
||||||
*/
|
|
||||||
variant: PlaygroundFeatureSchemaVariant | null;
|
|
||||||
}
|
|
||||||
export interface PlaygroundResponseSchema {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {PlaygroundRequestSchema}
|
|
||||||
* @memberof PlaygroundResponseSchema
|
|
||||||
*/
|
|
||||||
input: PlaygroundRequestSchema;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<PlaygroundFeatureSchema>}
|
|
||||||
* @memberof PlaygroundResponseSchema
|
|
||||||
*/
|
|
||||||
features: Array<PlaygroundFeatureSchema>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaygroundRequestSchema {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PlaygroundRequestSchema
|
|
||||||
*/
|
|
||||||
environment: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type Array<string> | string
|
|
||||||
* @memberof PlaygroundRequestSchema
|
|
||||||
*/
|
|
||||||
projects?: Array<string> | string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {SdkContextSchema}
|
|
||||||
* @memberof PlaygroundRequestSchema
|
|
||||||
*/
|
|
||||||
context: SdkContextSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SdkContextSchema {
|
|
||||||
[key: string]: string | any;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof SdkContextSchema
|
|
||||||
*/
|
|
||||||
appName: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Date}
|
|
||||||
* @memberof SdkContextSchema
|
|
||||||
*/
|
|
||||||
currentTime?: Date;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof SdkContextSchema
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
environment?: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {{ [key: string]: string; }}
|
|
||||||
* @memberof SdkContextSchema
|
|
||||||
*/
|
|
||||||
properties?: { [key: string]: string };
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof SdkContextSchema
|
|
||||||
*/
|
|
||||||
remoteAddress?: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof SdkContextSchema
|
|
||||||
*/
|
|
||||||
sessionId?: string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof SdkContextSchema
|
|
||||||
*/
|
|
||||||
userId?: string;
|
|
||||||
}
|
|
@ -2,7 +2,7 @@ import useAPI from '../useApi/useApi';
|
|||||||
import {
|
import {
|
||||||
PlaygroundRequestSchema,
|
PlaygroundRequestSchema,
|
||||||
PlaygroundResponseSchema,
|
PlaygroundResponseSchema,
|
||||||
} from './playground.model';
|
} from '../../../../component/playground/Playground/interfaces/playground.model';
|
||||||
|
|
||||||
export const usePlaygroundApi = () => {
|
export const usePlaygroundApi = () => {
|
||||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||||
|
@ -9,7 +9,7 @@ const useHiddenColumns = (
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hidden = condition ? hiddenColumns : [];
|
const hidden = condition ? hiddenColumns : [];
|
||||||
setHiddenColumns(hidden);
|
setHiddenColumns(hidden);
|
||||||
}, [setHiddenColumns, condition]);
|
}, [setHiddenColumns, hiddenColumns, condition]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useHiddenColumns;
|
export default useHiddenColumns;
|
||||||
|
Loading…
Reference in New Issue
Block a user