1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-13 11:17:26 +02:00

bug fixes, improvements and env handling

This commit is contained in:
andreas-unleash 2022-08-02 11:57:23 +03:00
parent 9fee273cff
commit 42fbc27148
19 changed files with 361 additions and 253 deletions

View File

@ -18,14 +18,14 @@ import {
import { useStyles } from '../ConstraintAccordion.styles';
import {
PlaygroundFeatureStrategyConstraintResult,
SdkContextSchema,
PlaygroundRequestSchema,
} from '../../../../hooks/api/actions/usePlayground/playground.model';
interface IConstraintAccordionViewProps {
constraint: IConstraint | PlaygroundFeatureStrategyConstraintResult;
onDelete?: () => void;
onEdit?: () => void;
playgroundContext?: SdkContextSchema;
playgroundInput?: PlaygroundRequestSchema;
maxLength?: number;
sx?: SxProps<Theme>;
}
@ -36,7 +36,7 @@ export const ConstraintAccordionView = ({
onDelete,
sx = undefined,
maxLength,
playgroundContext,
playgroundInput,
}: IConstraintAccordionViewProps) => {
const { classes: styles } = useStyles();
const [expandable, setExpandable] = useState(true);
@ -46,7 +46,6 @@ export const ConstraintAccordionView = ({
[...semVerOperators, ...numOperators, ...dateOperators],
constraint.operator
);
const handleClick = () => {
if (expandable) {
setExpanded(!expanded);
@ -79,7 +78,7 @@ export const ConstraintAccordionView = ({
allowExpand={setExpandable}
expanded={expanded}
maxLength={maxLength ?? 112}
playgroundContext={playgroundContext}
playgroundInput={playgroundInput}
/>
</AccordionSummary>

View File

@ -5,7 +5,7 @@ import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeade
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
import {
PlaygroundFeatureStrategyConstraintResult,
SdkContextSchema,
PlaygroundRequestSchema,
} from '../../../../../hooks/api/actions/usePlayground/playground.model';
interface IConstraintAccordionViewHeaderProps {
@ -15,7 +15,7 @@ interface IConstraintAccordionViewHeaderProps {
singleValue: boolean;
expanded: boolean;
allowExpand: (shouldExpand: boolean) => void;
playgroundContext?: SdkContextSchema;
playgroundInput?: PlaygroundRequestSchema;
maxLength?: number;
}
@ -27,7 +27,7 @@ export const ConstraintAccordionViewHeader = ({
allowExpand,
expanded,
maxLength,
playgroundContext,
playgroundInput,
}: IConstraintAccordionViewHeaderProps) => {
const { classes: styles } = useStyles();
@ -41,7 +41,7 @@ export const ConstraintAccordionViewHeader = ({
expanded={expanded}
result={'result' in constraint ? constraint.result : undefined}
maxLength={maxLength}
playgroundContext={playgroundContext}
playgroundInput={playgroundInput}
/>
<ConstraintAccordionHeaderActions
onEdit={onEdit}

View File

@ -7,7 +7,7 @@ import React from 'react';
import { IConstraint } from '../../../../../../interfaces/strategy';
import { useStyles } from '../../../ConstraintAccordion.styles';
import { CancelOutlined } from '@mui/icons-material';
import { SdkContextSchema } from '../../../../../../hooks/api/actions/usePlayground/playground.model';
import { PlaygroundRequestSchema } from '../../../../../../hooks/api/actions/usePlayground/playground.model';
const StyledHeaderText = styled('span')(({ theme }) => ({
display: '-webkit-box',
@ -43,7 +43,7 @@ interface ConstraintAccordionViewHeaderMetaInfoProps {
allowExpand: (shouldExpand: boolean) => void;
result?: boolean;
maxLength?: number;
playgroundContext?: SdkContextSchema;
playgroundInput?: PlaygroundRequestSchema;
}
export const ConstraintAccordionViewHeaderInfo = ({
@ -52,12 +52,17 @@ export const ConstraintAccordionViewHeaderInfo = ({
allowExpand,
expanded,
result,
playgroundInput,
maxLength = 112,
playgroundContext,
}: ConstraintAccordionViewHeaderMetaInfoProps) => {
const { classes: styles } = useStyles();
const theme = useTheme();
const isPlayground = Boolean(playgroundInput);
const constrainExistsInContext =
isPlayground &&
Boolean(playgroundInput?.context[constraint.contextName]);
return (
<StyledHeaderWrapper>
<div className={styles.headerMetaInfo}>
@ -65,24 +70,17 @@ export const ConstraintAccordionViewHeaderInfo = ({
<StyledHeaderText>
{constraint.contextName}
<ConditionallyRender
condition={
result !== undefined &&
Boolean(playgroundContext)
}
condition={isPlayground}
show={
<Typography
variant={'body1'}
color={
Boolean(
playgroundContext![
constraint.contextName
]
)
constrainExistsInContext
? theme.palette.neutral.dark
: theme.palette.error.main
}
>
{playgroundContext![
{playgroundInput?.context[
constraint.contextName
] || 'no value'}
</Typography>

View File

@ -203,6 +203,7 @@ export const Playground: VFC<{}> = () => {
<PlaygroundResultsTable
loading={loading}
features={results?.features}
input={results?.input}
/>
}
elseShow={<PlaygroundGuidance />}

View File

@ -1,17 +1,21 @@
import {
PlaygroundFeatureSchema,
PlaygroundFeatureStrategyResult,
PlaygroundRequestSchema,
} from 'hooks/api/actions/usePlayground/playground.model';
import { IconButton, Popover, styled, Typography } from '@mui/material';
import { IconButton, Popover, styled } from '@mui/material';
import { InfoOutlined } from '@mui/icons-material';
import React, { useRef, useState } from 'react';
import { ConditionallyRender } from '../../../../common/ConditionallyRender/ConditionallyRender';
import { PlaygroundResultFeatureStrategyItem } from './PlaygroundResultFeatureStrategyItem/PlaygroundResultFeatureStrategyItem';
import { useStyles } from './FeatureResultInfoPopoverCell.styles';
import { PlaygroundResultFeatureDetails } from './PlaygroundResultFeatureDetails/PlaygroundResultFeatureDetails';
import {
PlaygroundResultStrategyList,
WrappedPlaygroundResultStrategyList,
} from './PlaygroundResultStrategyList/PlaygroundResultStrategyList';
interface FeatureResultInfoPopoverCellProps {
feature?: PlaygroundFeatureSchema;
feature: PlaygroundFeatureSchema;
input?: PlaygroundRequestSchema;
}
const FeatureResultPopoverWrapper = styled('div')(({ theme }) => ({
@ -21,6 +25,7 @@ const FeatureResultPopoverWrapper = styled('div')(({ theme }) => ({
export const FeatureResultInfoPopoverCell = ({
feature,
input,
}: FeatureResultInfoPopoverCellProps) => {
const [open, setOpen] = useState(false);
const { classes: styles } = useStyles();
@ -30,77 +35,6 @@ export const FeatureResultInfoPopoverCell = ({
setOpen(!open);
};
const dummyPlaygroundFeatureTrue: PlaygroundFeatureSchema = {
name: feature?.name as any,
projectId: 'default',
isEnabled: true,
} as any;
const dummyPlaygroundFeatureFalse: PlaygroundFeatureSchema = {
name: feature?.name as any,
projectId: 'default',
isEnabled: false,
} as any;
const strategies: PlaygroundFeatureStrategyResult[] = [
{
name: 'default',
id: 'strategy-id',
parameters: {},
result: feature?.isEnabled as any,
constraints: [
{
result: true,
contextName: 'appName',
operator: 'IN',
caseInsensitive: false,
inverted: false,
values: ['MyApp', 'MyOtherApp', 'Unleash'],
},
],
segments: [
{
result: feature?.isEnabled as any,
id: 5,
name: 'my-segment',
constraints: [
{
result: feature?.isEnabled as any,
contextName: 'environment',
operator: 'IN',
caseInsensitive: false,
inverted: false,
values: ['development'],
},
],
},
],
},
{
name: 'flexibleRollout',
id: 'strategy-id',
parameters: {},
result: false,
segments: [
{
result: false,
id: 6,
name: 'my-segment',
constraints: [
{
result: false,
contextName: 'appName',
operator: 'IN',
caseInsensitive: false,
inverted: false,
values: ['MyApp2'],
},
],
},
],
},
];
if (!feature) {
return null;
}
@ -125,29 +59,24 @@ export const FeatureResultInfoPopoverCell = ({
classes={{ paper: styles.popoverPaper }}
>
<PlaygroundResultFeatureDetails
feature={
feature.isEnabled
? dummyPlaygroundFeatureTrue
: dummyPlaygroundFeatureFalse
}
feature={feature}
input={input}
onClose={() => setOpen(false)}
/>
<ConditionallyRender
condition={strategies.length > 0}
condition={!feature.isEnabledInCurrentEnvironment}
show={
<>
<Typography
variant={'subtitle1'}
sx={{ mb: 2, color: 'text.secondary' }}
>{`Strategies (${strategies.length})`}</Typography>
{strategies.map((strategy, index) => (
<PlaygroundResultFeatureStrategyItem
key={strategy.id}
strategy={strategy}
index={index}
/>
))}
</>
<PlaygroundResultStrategyList
strategies={feature?.strategies}
input={input}
/>
}
elseShow={
<WrappedPlaygroundResultStrategyList
strategies={feature?.strategies}
feature={feature}
input={input}
/>
}
/>
</Popover>

View File

@ -9,9 +9,11 @@ export const useStyles = makeStyles()(theme => ({
titleRow: {
display: 'inline-flex',
alignItems: 'flex-start',
justifyItems: 'center',
justifyContent: 'center',
gap: '12px',
marginTop: '12px',
},
alertRow: {},
descriptionRow: {
marginBottom: theme.spacing(2),
},

View File

@ -1,30 +1,45 @@
import { PlaygroundFeatureSchema } from '../../../../../../hooks/api/actions/usePlayground/playground.model';
import { IconButton, Typography, useTheme } from '@mui/material';
import {
PlaygroundFeatureSchema,
PlaygroundRequestSchema,
} from '../../../../../../hooks/api/actions/usePlayground/playground.model';
import { Alert, IconButton, Typography, useTheme } from '@mui/material';
import { PlaygroundResultChip } from '../../PlaygroundResultChip/PlaygroundResultChip';
import { useStyles } from './PlaygroundResultFeatureDetails.styles';
import { CloseOutlined } from '@mui/icons-material';
import React from 'react';
import { ConditionallyRender } from '../../../../../common/ConditionallyRender/ConditionallyRender';
import { checkForEmptyValues } from './helpers';
interface PlaygroundFeatureResultDetailsProps {
feature: PlaygroundFeatureSchema;
input?: PlaygroundRequestSchema;
onClose: () => void;
}
export const PlaygroundResultFeatureDetails = ({
feature,
input,
onClose,
}: PlaygroundFeatureResultDetailsProps) => {
const { classes: styles } = useStyles();
const theme = useTheme();
const description = feature.isEnabled
? 'This feature toggle is True in production because '
: 'This feature toggle is False in production because ';
const reason = feature.isEnabled
const description = Boolean(feature.isEnabled)
? `This feature toggle is True in ${input?.environment} because `
: `This feature toggle is False in ${input?.environment} because `;
const reason = Boolean(feature.isEnabled)
? 'at least one strategy is True'
: feature?.isEnabledInCurrentEnvironment
? 'the environment is disabled'
: 'all strategies are False';
const color = feature.isEnabled
const color = Boolean(feature.isEnabled)
? theme.palette.success.main
: theme.palette.error.main;
const noValueTxt = checkForEmptyValues(input?.context)
? 'You did not provide a value for your context filed in step 2 of the configuration'
: undefined;
const onCloseClick =
onClose &&
((event: React.SyntheticEvent) => {
@ -55,6 +70,14 @@ export const PlaygroundResultFeatureDetails = ({
{reason}
</Typography>
</div>
<ConditionallyRender
condition={Boolean(noValueTxt)}
show={
<div className={styles.alertRow}>
<Alert color={'info'}>{noValueTxt}</Alert>
</div>
}
/>
</>
);
};

View File

@ -0,0 +1,8 @@
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
);
}

View File

@ -1,16 +1,18 @@
import {
PlaygroundFeatureStrategyConstraintResult,
SdkContextSchema,
PlaygroundRequestSchema,
} from 'hooks/api/actions/usePlayground/playground.model';
import React, { Fragment } from 'react';
import { objectId } from '../../../../../../utils/objectId';
import { ConditionallyRender } from '../../../../../common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from '../../../../../common/StrategySeparator/StrategySeparator';
import { ConstraintAccordionView } from '../../../../../common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView';
import { objectId } from '../../../../../../../utils/objectId';
import { ConditionallyRender } from '../../../../../../common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from '../../../../../../common/StrategySeparator/StrategySeparator';
import { ConstraintAccordionView } from '../../../../../../common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView';
import { styled } from '@mui/material';
interface PlaygroundResultConstraintExecutionProps {
constraints?: PlaygroundFeatureStrategyConstraintResult[];
compact: boolean;
input?: PlaygroundRequestSchema;
}
export const PlaygroundResultConstraintExecutionWrapper = styled('div')(
@ -23,18 +25,9 @@ export const PlaygroundResultConstraintExecutionWrapper = styled('div')(
export const PlaygroundResultConstraintExecution = ({
constraints,
compact,
input,
}: PlaygroundResultConstraintExecutionProps) => {
// const context = usePlaygroundContext();
const contextFalse: SdkContextSchema = {
appName: 'MyApp',
environment: '',
};
const contextTrue: SdkContextSchema = {
appName: 'MyApp',
environment: 'development',
};
if (!constraints) return null;
return (
@ -47,12 +40,8 @@ export const PlaygroundResultConstraintExecution = ({
/>
<ConstraintAccordionView
constraint={constraint}
playgroundContext={
Boolean(constraint.result)
? contextTrue
: contextFalse
}
maxLength={80}
playgroundInput={input}
maxLength={compact ? 25 : 50}
sx={{
backgroundColor: 'transparent!important',
}}

View File

@ -28,6 +28,7 @@ export const useStyles = makeStyles()(theme => ({
[theme.breakpoints.down(400)]: {
padding: '0.5rem',
},
width: '100%',
paddingBottom: '1rem',
borderRadius: theme.shape.borderRadiusMedium,
background: theme.palette.background.default,

View File

@ -1,4 +1,4 @@
import { Box, useTheme } from '@mui/material';
import { Box, styled, Typography, useTheme } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import {
@ -7,18 +7,33 @@ import {
} from 'utils/strategyNames';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { PlaygroundResultChip } from '../../PlaygroundResultChip/PlaygroundResultChip';
import { PlaygroundFeatureStrategyResult } from 'hooks/api/actions/usePlayground/playground.model';
import { PlaygroundResultStrategyExecution } from '../PlaygroundResultStrategyExecution/PlaygroundResultStrategyExecution';
import {
PlaygroundFeatureStrategyResult,
PlaygroundRequestSchema,
} from 'hooks/api/actions/usePlayground/playground.model';
import { PlaygroundResultStrategyExecution } from './PlaygroundResultStrategyExecution/PlaygroundResultStrategyExecution';
import { useStyles } from './PlaygroundResultFeatureStrategyItem.styles';
interface IPlaygroundResultFeatureStrategyItemProps {
strategy: PlaygroundFeatureStrategyResult;
index: number;
input?: PlaygroundRequestSchema;
compact: boolean;
}
const StyledItemWrapper = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: '4px',
gap: '4px',
}));
export const PlaygroundResultFeatureStrategyItem = ({
strategy,
input,
index,
compact,
}: IPlaygroundResultFeatureStrategyItemProps) => {
const { result, name } = strategy;
const { classes: styles } = useStyles();
@ -31,34 +46,44 @@ export const PlaygroundResultFeatureStrategyItem = ({
: `1px solid ${theme.palette.divider}`;
return (
<Box key={strategy.id} sx={{ width: '100%', position: 'relative' }}>
<Box
sx={{
width: '100%',
position: 'relative',
paddingRight: compact ? '12px' : 0,
}}
>
<ConditionallyRender
condition={index > 0}
show={<StrategySeparator text="OR" />}
/>
<Box className={styles.innerContainer} sx={{ border }}>
<div className={styles.header}>
<div className={styles.headerName}>
<Icon className={styles.icon} />
<StringTruncator
maxWidth="150"
maxLength={15}
text={formatStrategyName(name)}
<StyledItemWrapper>
<Typography variant={'subtitle1'}>{index + 1}</Typography>
<Box className={styles.innerContainer} sx={{ border }}>
<div className={styles.header}>
<div className={styles.headerName}>
<Icon className={styles.icon} />
<StringTruncator
maxWidth="150"
maxLength={15}
text={formatStrategyName(name)}
/>
</div>
<PlaygroundResultChip
showIcon={false}
enabled={Boolean(result)}
label={label}
/>
</div>
<PlaygroundResultChip
showIcon={false}
enabled={Boolean(result)}
label={label}
/>
</div>
<div className={styles.body}>
<PlaygroundResultStrategyExecution
strategyResult={strategy}
percentageFill={theme.palette.tertiary.light}
/>
</div>
</Box>
<div className={styles.body}>
<PlaygroundResultStrategyExecution
strategyResult={strategy}
input={input}
percentageFill={theme.palette.tertiary.light}
/>
</div>
</Box>
</StyledItemWrapper>
</Box>
);
};

View File

@ -1,14 +1,19 @@
import { PlaygroundFeatureStrategySegmentResult } from '../../../../../../hooks/api/actions/usePlayground/playground.model';
import {
PlaygroundFeatureStrategySegmentResult,
PlaygroundRequestSchema,
} from '../../../../../../../hooks/api/actions/usePlayground/playground.model';
import { PlaygroundResultConstraintExecution } from '../PlaygroundResultConstraintExecution/PlaygroundResultConstraintExecution';
import { CancelOutlined, DonutLarge } from '@mui/icons-material';
import { Link } from 'react-router-dom';
import { StrategySeparator } from '../../../../../common/StrategySeparator/StrategySeparator';
import { StrategySeparator } from '../../../../../../common/StrategySeparator/StrategySeparator';
import { useStyles } from './PlaygroundResultSegmentExecution.styles';
import { styled, Typography } from '@mui/material';
import { ConditionallyRender } from '../../../../../common/ConditionallyRender/ConditionallyRender';
import { ConditionallyRender } from '../../../../../../common/ConditionallyRender/ConditionallyRender';
interface PlaygroundResultSegmentExecutionProps {
segments?: PlaygroundFeatureStrategySegmentResult[];
input?: PlaygroundRequestSchema;
hasConstraints: boolean;
}
const SegmentExecutionLinkWrapper = styled('div')(({ theme }) => ({
@ -55,8 +60,11 @@ const SegmentResultTextWrapper = styled('div')(({ theme }) => ({
export const PlaygroundResultSegmentExecution = ({
segments,
input,
hasConstraints,
}: PlaygroundResultSegmentExecutionProps) => {
const { classes: styles } = useStyles();
if (!segments) return null;
return (
<>
@ -93,10 +101,14 @@ export const PlaygroundResultSegmentExecution = ({
<SegmentExecutionConstraintWrapper>
<PlaygroundResultConstraintExecution
constraints={segment.constraints}
compact={true}
input={input}
/>
</SegmentExecutionConstraintWrapper>
<ConditionallyRender
condition={index < segments?.length - 1}
condition={
index === segments?.length - 1 && hasConstraints
}
show={<StrategySeparator text="AND" sx={{ pt: 1 }} />}
/>
</SegmentExecutionWrapper>

View File

@ -10,7 +10,7 @@ export const useStyles = makeStyles()(theme => ({
color: theme.palette.grey[700],
},
summary: {
width: '100%',
width: 'auto',
padding: theme.spacing(2, 3),
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,

View File

@ -1,9 +1,12 @@
import { ConditionallyRender } from '../../../../../common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from '../../../../../common/StrategySeparator/StrategySeparator';
import { Box, Chip } from '@mui/material';
import { ConditionallyRender } from '../../../../../../common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from '../../../../../../common/StrategySeparator/StrategySeparator';
import { Box, Chip, styled } from '@mui/material';
import { useStyles } from './PlaygroundResultStrategyExecution.styles';
import { PlaygroundFeatureStrategyResult } from '../../../../../../hooks/api/actions/usePlayground/playground.model';
import useUiConfig from '../../../../../../hooks/api/getters/useUiConfig/useUiConfig';
import {
PlaygroundFeatureStrategyResult,
PlaygroundRequestSchema,
} from '../../../../../../../hooks/api/actions/usePlayground/playground.model';
import useUiConfig from '../../../../../../../hooks/api/getters/useUiConfig/useUiConfig';
import React from 'react';
import { PlaygroundResultConstraintExecution } from '../PlaygroundResultConstraintExecution/PlaygroundResultConstraintExecution';
import { PlaygroundResultSegmentExecution } from '../PlaygroundResultSegmentExecution/PlaygroundResultSegmentExecution';
@ -11,24 +14,38 @@ import { PlaygroundResultSegmentExecution } from '../PlaygroundResultSegmentExec
interface PlaygroundResultStrategyExecutionProps {
strategyResult: PlaygroundFeatureStrategyResult;
percentageFill?: string;
input?: PlaygroundRequestSchema;
}
const StyledStrategyExecutionWrapper = styled('div')(({ theme }) => ({
padding: theme.spacing(1),
}));
export const PlaygroundResultStrategyExecution = ({
strategyResult,
input,
}: PlaygroundResultStrategyExecutionProps) => {
const { name, constraints, segments } = strategyResult;
const { uiConfig } = useUiConfig();
const { classes: styles } = useStyles();
const hasConstraints = Boolean(constraints && constraints.length > 0);
return (
<>
<StyledStrategyExecutionWrapper>
<ConditionallyRender
condition={
Boolean(uiConfig.flags.SE) &&
Boolean(segments && segments.length > 0)
}
show={<PlaygroundResultSegmentExecution segments={segments} />}
show={
<PlaygroundResultSegmentExecution
segments={segments}
hasConstraints={hasConstraints}
input={input}
/>
}
/>
<ConditionallyRender
condition={Boolean(constraints && constraints.length > 0)}
@ -36,6 +53,8 @@ export const PlaygroundResultStrategyExecution = ({
<>
<PlaygroundResultConstraintExecution
constraints={constraints}
compact={true}
input={input}
/>
<StrategySeparator text="AND" />
</>
@ -56,6 +75,6 @@ export const PlaygroundResultStrategyExecution = ({
</Box>
}
/>
</>
</StyledStrategyExecutionWrapper>
);
};

View File

@ -0,0 +1,88 @@
import {
PlaygroundFeatureSchema,
PlaygroundFeatureStrategyResult,
PlaygroundRequestSchema,
} from '../../../../../../hooks/api/actions/usePlayground/playground.model';
import { ConditionallyRender } from '../../../../../common/ConditionallyRender/ConditionallyRender';
import { Alert, styled, Typography } from '@mui/material';
import { PlaygroundResultFeatureStrategyItem } from '../PlaygroundResultFeatureStrategyItem/PlaygroundResultFeatureStrategyItem';
interface PlaygroundResultStrategyListProps {
strategies: PlaygroundFeatureStrategyResult[];
input?: PlaygroundRequestSchema;
compact?: boolean;
}
export const PlaygroundResultStrategyList = ({
strategies,
input,
compact = false,
}: PlaygroundResultStrategyListProps) => {
return (
<ConditionallyRender
condition={strategies.length > 0}
show={
<>
<Typography
variant={'subtitle1'}
sx={{ mt: 2, ml: 1, mb: 2, color: 'text.secondary' }}
>{`Strategies (${strategies.length})`}</Typography>
{strategies.map((strategy, index) => (
<PlaygroundResultFeatureStrategyItem
key={strategy.id}
strategy={strategy}
index={index}
compact={compact}
input={input}
/>
))}
</>
}
/>
);
};
const StyledAlertWrapper = styled('div')(({ theme }) => ({
width: '100%',
display: 'flex',
flexDirection: 'column',
borderRadius: theme.shape.borderRadiusMedium,
border: `1px solid ${theme.palette.info.main}`,
}));
const StyledListWrapper = styled('div')(({ theme }) => ({
padding: theme.spacing(1, 0.5),
}));
const StyledAlert = styled(Alert)(({ theme }) => ({
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
}));
interface WrappedPlaygroundResultStrategyListProps
extends PlaygroundResultStrategyListProps {
feature: PlaygroundFeatureSchema;
}
export const WrappedPlaygroundResultStrategyList = ({
strategies,
feature,
input,
}: WrappedPlaygroundResultStrategyListProps) => {
return (
<StyledAlertWrapper>
<StyledAlert severity={'info'} color={'info'}>
If environment would be enabled then this feature would be{' '}
{feature.isEnabled ? 'TRUE' : 'FALSE'} and the strategies would
evaluate like this:{' '}
</StyledAlert>
<StyledListWrapper>
<PlaygroundResultStrategyList
strategies={strategies}
input={input}
compact
/>
</StyledListWrapper>
</StyledAlertWrapper>
);
};

View File

@ -6,7 +6,7 @@ import { ReactComponent as FeatureEnabledIcon } from '../../../../../assets/icon
import { ReactComponent as FeatureDisabledIcon } from '../../../../../assets/icons/isenabled-false.svg';
interface IResultChipProps {
enabled: boolean;
enabled: boolean | 'unevaluated';
// Result icon - defaults to true
showIcon?: boolean;
label?: string;
@ -53,7 +53,7 @@ export const PlaygroundResultChip = ({
const theme = useTheme();
const icon = (
<ConditionallyRender
condition={enabled}
condition={Boolean(enabled)}
show={
<FeatureEnabledIcon
color={theme.palette.success.main}
@ -73,7 +73,7 @@ export const PlaygroundResultChip = ({
return (
<ConditionallyRender
condition={enabled}
condition={Boolean(enabled)}
show={
<StyledTrueChip
icon={showIcon ? icon : undefined}

View File

@ -19,7 +19,10 @@ import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
import { useSearch } from 'hooks/useSearch';
import { createLocalStorage } from 'utils/createLocalStorage';
import { FeatureStatusCell } from './FeatureStatusCell/FeatureStatusCell';
import { PlaygroundFeatureSchema } from 'hooks/api/actions/usePlayground/playground.model';
import {
PlaygroundFeatureSchema,
PlaygroundRequestSchema,
} from 'hooks/api/actions/usePlayground/playground.model';
import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
import useLoading from 'hooks/useLoading';
import { VariantCell } from './VariantCell/VariantCell';
@ -33,11 +36,13 @@ const { value, setValue } = createLocalStorage(
interface IPlaygroundResultsTableProps {
features?: PlaygroundFeatureSchema[];
input?: PlaygroundRequestSchema;
loading: boolean;
}
export const PlaygroundResultsTable = ({
features,
input,
loading,
}: IPlaygroundResultsTableProps) => {
const [searchParams, setSearchParams] = useSearchParams();
@ -49,6 +54,75 @@ export const PlaygroundResultsTable = ({
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
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: ({ value }: any) => <FeatureStatusCell enabled={value} />,
sortType: 'boolean',
sortInverted: true,
},
{
Header: '',
id: 'info',
Cell: ({ row }: any) => (
<FeatureResultInfoPopoverCell
feature={row.original}
input={input}
/>
),
},
];
}, [input]);
const {
data: searchedData,
getSearchText,
@ -236,67 +310,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,
},
{
Header: '',
id: 'info',
Cell: ({ row }: any) => (
<FeatureResultInfoPopoverCell feature={row.original} />
),
},
];

View File

@ -65,13 +65,17 @@ export interface PlaygroundFeatureSchema {
* @type {boolean}
* @memberof PlaygroundFeatureSchema
*/
isEnabled: boolean;
isEnabled: boolean | 'unevaluated';
isEnabledInCurrentEnvironment: boolean;
/**
*
* @type {PlaygroundFeatureSchemaVariant}
* @memberof PlaygroundFeatureSchema
*/
variant: PlaygroundFeatureSchemaVariant | null;
strategies: PlaygroundFeatureStrategyResult[];
}
export interface PlaygroundResponseSchema {
/**