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:
parent
562ca62b42
commit
e894cbb52a
@ -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,
|
||||
|
@ -24,6 +24,9 @@ export const useStyles = makeStyles()(theme => ({
|
||||
marginLeft: 'auto',
|
||||
display: 'flex',
|
||||
},
|
||||
resultChip: {
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
body: {
|
||||
padding: theme.spacing(2),
|
||||
justifyItems: 'center',
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
|
@ -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} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
@ -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}/>}
|
||||
/>
|
||||
);
|
||||
}
|
@ -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[];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user