1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-09 00:18:00 +01:00

Playground result info modal initial

This commit is contained in:
andreas-unleash 2022-07-28 15:16:40 +03:00
parent 562ca62b42
commit e894cbb52a
10 changed files with 299 additions and 175 deletions

View File

@ -3,19 +3,11 @@ import React, { ReactNode } from 'react';
interface IIconCellProps {
icon: ReactNode;
onClick?: () => void;
}
export const IconCell = ({ icon, onClick }: IIconCellProps) => {
const handleClick =
onClick &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onClick();
});
export const IconCell = ({ icon }: IIconCellProps) => {
return (
<Box
onClick={handleClick}
sx={{
pl: 2,
pr: 1,

View File

@ -24,6 +24,9 @@ export const useStyles = makeStyles()(theme => ({
marginLeft: 'auto',
display: 'flex',
},
resultChip: {
marginLeft: 'auto',
},
body: {
padding: theme.spacing(2),
justifyItems: 'center',

View File

@ -1,7 +1,7 @@
import { DragIndicator, Edit } from '@mui/icons-material';
import { styled, useTheme, IconButton } from '@mui/material';
import { styled, useTheme, IconButton, Chip } from '@mui/material';
import { Link } from 'react-router-dom';
import { IFeatureStrategy } from 'interfaces/strategy';
import {IFeatureStrategy, IPlaygroundFeatureStrategyResult} from 'interfaces/strategy';
import {
getFeatureStrategyIcon,
formatStrategyName,
@ -18,8 +18,10 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
interface IStrategyItemProps {
environmentId: string;
strategy: IFeatureStrategy;
strategy: IFeatureStrategy | IPlaygroundFeatureStrategyResult;
isDraggable?: boolean;
showActions?: boolean;
result?: boolean;
}
const DragIcon = styled(IconButton)(({ theme }) => ({
@ -32,6 +34,8 @@ export const StrategyItem = ({
environmentId,
strategy,
isDraggable,
showActions = true,
result,
}: IStrategyItemProps) => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
@ -46,6 +50,8 @@ export const StrategyItem = ({
strategy.id
);
const showShouldShowResultChip = result !== undefined;
return (
<div className={styles.container}>
<div className={styles.header}>
@ -66,25 +72,36 @@ export const StrategyItem = ({
maxLength={15}
text={formatStrategyName(strategy.name)}
/>
<div className={styles.actions}>
<PermissionIconButton
permission={UPDATE_FEATURE_STRATEGY}
environmentId={environmentId}
projectId={projectId}
component={Link}
to={editStrategyPath}
tooltipProps={{ title: 'Edit strategy' }}
>
<Edit />
</PermissionIconButton>
<FeatureStrategyRemove
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
strategyId={strategy.id}
icon
/>
</div>
<ConditionallyRender
condition={showActions}
show={
<div className={styles.actions}>
<PermissionIconButton
permission={UPDATE_FEATURE_STRATEGY}
environmentId={environmentId}
projectId={projectId}
component={Link}
to={editStrategyPath}
tooltipProps={{ title: 'Edit strategy' }}
>
<Edit />
</PermissionIconButton>
<FeatureStrategyRemove
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
strategyId={strategy.id}
icon
/>
</div>
}
/>
<ConditionallyRender
condition={showShouldShowResultChip}
show={
<Featur>
}
/>
</div>
<div className={styles.body}>
<StrategyExecution

View File

@ -1,24 +0,0 @@
import { PlaygroundFeatureSchema } from '../../../../hooks/api/actions/usePlayground/playground.model';
import { Modal } from '@mui/material';
interface PlaygroundFeatureResultInfoModalProps {
feature?: PlaygroundFeatureSchema;
open: boolean;
setOpen: (open: boolean) => void;
}
export const PlaygroundFeatureResultInfoModal = ({
feature,
open,
setOpen,
}: PlaygroundFeatureResultInfoModalProps) => {
if (!feature) {
return null;
}
return (
<Modal open={open} onClose={() => setOpen(false)}>
<p>Test</p>
</Modal>
);
};

View File

@ -0,0 +1,85 @@
import { PlaygroundFeatureSchema } from '../../../../../hooks/api/actions/usePlayground/playground.model';
import { Box, IconButton, Popover } from '@mui/material';
import { InfoOutlined } from '@mui/icons-material';
import { IconCell } from '../../../../common/Table/cells/IconCell/IconCell';
import React, { useRef, useState } from 'react';
interface FeatureResultInfoPopoverCellProps {
feature?: PlaygroundFeatureSchema;
}
export const FeatureResultInfoPopoverCell = ({
feature,
}: FeatureResultInfoPopoverCellProps) => {
if (!feature) {
return null;
}
const [open, setOpen] = useState(false);
const ref = useRef(null);
const togglePopover = (event: React.SyntheticEvent) => {
setOpen(!open);
};
const strategies = [
{
type: 'standard',
id: 'strategy-id',
result: false,
constraints: [
{
result: false,
contextName: 'appName',
operator: 'IN',
caseInsensitive: true,
inverted: false,
values: ['a', 'b'],
},
],
segments: [
{
result: true,
id: 5,
name: 'my-segment',
constraints: [
{
result: false,
contextName: 'appName',
operator: 'IN',
caseInsensitive: true,
inverted: false,
values: ['a', 'b'],
},
],
},
],
},
{
type: 'default',
result: true,
},
];
return (
<>
<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',
}}
>
{feature.name}
</Popover>
</>
);
};

View File

@ -0,0 +1,38 @@
import { ConditionallyRender } from '../../../../../common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from '../../../../../common/StrategySeparator/StrategySeparator';
import { StrategyItem } from '../../../../../feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem';
import {
IConstraint,
IFeatureStrategy, IPlaygroundFeatureStrategyResult,
} from '../../../../../../interfaces/strategy';
import { ISegment } from '../../../../../../interfaces/segment';
interface IPlaygroundResultFeatureStrategyItemProps {
strategy: IPlaygroundFeatureStrategyResult;
environmentName: string;
index: number;
}
export const PlaygroundResultFeatureStrategyItem = ({
strategy,
environmentName,
index,
}: IPlaygroundResultFeatureStrategyItemProps) => {
const { result } = strategy;
return (
<div key={strategy.id} className={``}>
<ConditionallyRender
condition={index > 0}
show={<StrategySeparator text="OR" />}
/>
<StrategyItem
strategy={strategy}
result={result}
environmentId={environmentName}
isDraggable={false}
/>
</div>
);
};

View File

@ -4,37 +4,12 @@ import { ReactComponent as FeatureEnabledIcon } from 'assets/icons/isenabled-tru
import { ReactComponent as FeatureDisabledIcon } from 'assets/icons/isenabled-false.svg';
import { Box, Chip, styled, useTheme } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import {ResultChip} from "../ResultChip/ResultChip";
interface IFeatureStatusCellProps {
enabled: boolean;
}
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 }) => ({
display: 'flex',
alignItems: 'center',
@ -70,11 +45,7 @@ export const FeatureStatusCell = ({ enabled }: IFeatureStatusCellProps) => {
return (
<StyledCellBox>
<StyledChipWrapper data-loading>
<ConditionallyRender
condition={enabled}
show={<StyledTrueChip icon={icon} label={label} />}
elseShow={<StyledFalseChip icon={icon} label={label} />}
/>
<ResultChip enabled label={label} icon={icon} />
</StyledChipWrapper>
</StyledCellBox>
);

View File

@ -25,7 +25,7 @@ import useLoading from 'hooks/useLoading';
import { VariantCell } from './VariantCell/VariantCell';
import { IconCell } from '../../../common/Table/cells/IconCell/IconCell';
import { InfoOutlined } from '@mui/icons-material';
import { PlaygroundFeatureResultInfoModal } from '../PlaygroundFeatureResultInfoModal/PlaygroundFeatureResultInfoModal';
import { FeatureResultInfoPopoverCell } from './FeatureResultInfoPopoverCell/FeatureResultInfoPopoverCell';
const defaultSort: SortingRule<string> = { id: 'name' };
const { value, setValue } = createLocalStorage(
@ -51,87 +51,11 @@ export const PlaygroundResultsTable = ({
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const [selectedFeature, setSelectedFeature] = useState<
PlaygroundFeatureSchema | undefined
>();
const [showResultInfoModal, setShowResultInfoModal] = useState(false);
const columns = useMemo(
() => [
{
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) => (
<IconCell
icon={<InfoOutlined />}
onClick={() => onFeatureResultInfoClick(row.original)}
/>
),
},
],
//eslint-disable-next-line
[]
);
const {
data: searchedData,
getSearchText,
getSearchContext,
} = useSearch(columns, searchValue, features || []);
} = useSearch(COLUMNS, searchValue, features || []);
const data = useMemo(() => {
return loading
@ -166,7 +90,7 @@ export const PlaygroundResultsTable = ({
} = useTable(
{
initialState,
columns: columns as any,
columns: COLUMNS as any,
data: data as any,
sortTypes,
autoResetGlobalFilter: false,
@ -217,18 +141,8 @@ export const PlaygroundResultsTable = ({
// eslint-disable-next-line react-hooks/exhaustive-deps -- don't re-render after search params change
}, [loading, sortBy, searchValue]);
const onFeatureResultInfoClick = (feature: PlaygroundFeatureSchema) => {
setSelectedFeature(feature);
setShowResultInfoModal(true);
};
return (
<>
<PlaygroundFeatureResultInfoModal
feature={selectedFeature}
open={showResultInfoModal}
setOpen={setShowResultInfoModal}
/>
<Box
sx={{
display: 'flex',
@ -324,3 +238,67 @@ 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

@ -0,0 +1,46 @@
import {Box, Chip, styled} from "@mui/material";
import {colors} from "../../../../../themes/colors";
import {ConditionallyRender} from "../../../../common/ConditionallyRender/ConditionallyRender";
import React, {ReactElement} from "react";
interface IResultChipProps {
enabled: boolean;
icon?: ReactElement;
label?: string;
}
export 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,
},
}));
export 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,
},
}));
export const ResultChip = ({ enabled, icon, label}: IResultChipProps) => {
return (
<ConditionallyRender
condition={enabled}
show={<StyledTrueChip icon={Boolean(icon) ? icon : undefined} label={label}/>}
elseShow={<StyledFalseChip icon={Boolean(icon) ? icon : undefined} label={label}/>}
/>
);
}

View File

@ -1,4 +1,5 @@
import { Operator } from 'constants/operators';
import {ISegment} from "./segment";
export interface IFeatureStrategy {
id: string;
@ -56,3 +57,20 @@ export interface IFeatureStrategySortOrder {
id: string;
sortOrder: number;
}
export interface IPlaygroundFeatureStrategyConstraintResult extends IConstraint {
result: boolean;
}
export interface IPlaygroundFeatureStrategySegmentResult extends ISegment {
result: boolean;
}
export interface IPlaygroundFeatureStrategyResult {
type: string;
result: boolean;
id?: string;
constraints?: IPlaygroundFeatureStrategyConstraintResult[];
segments?: IPlaygroundFeatureStrategySegmentResult[];
}