mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
Disable and enable strategies - frontend (#3582)
Signed-off-by: andreas-unleash <andreas@getunleash.ai> Co-authored-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
1e3f652311
commit
3bb09c5ce4
@ -1,6 +1,5 @@
|
||||
import { FC, ReactNode } from 'react';
|
||||
import {
|
||||
hasNameField,
|
||||
IChange,
|
||||
IChangeRequest,
|
||||
IChangeRequestFeature,
|
||||
@ -8,18 +7,8 @@ import {
|
||||
import { objectId } from 'utils/objectId';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { Alert, Box, styled } from '@mui/material';
|
||||
|
||||
import {
|
||||
StrategyTooltipLink,
|
||||
StrategyDiff,
|
||||
} from 'component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink';
|
||||
import { StrategyExecution } from '../../../../feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
|
||||
import { ToggleStatusChange } from './ToggleStatusChange';
|
||||
import {
|
||||
StrategyAddedChange,
|
||||
StrategyDeletedChange,
|
||||
StrategyEditedChange,
|
||||
} from './StrategyChange';
|
||||
import { StrategyChange } from './StrategyChange';
|
||||
import { VariantPatch } from './VariantPatch/VariantPatch';
|
||||
|
||||
const StyledSingleChangeBox = styled(Box, {
|
||||
@ -74,6 +63,7 @@ export const Change: FC<{
|
||||
const lastIndex = feature.defaultChange
|
||||
? feature.changes.length + 1
|
||||
: feature.changes.length;
|
||||
|
||||
return (
|
||||
<StyledSingleChangeBox
|
||||
key={objectId(change)}
|
||||
@ -98,50 +88,17 @@ export const Change: FC<{
|
||||
discard={discard}
|
||||
/>
|
||||
)}
|
||||
{change.action === 'addStrategy' && (
|
||||
<>
|
||||
<StrategyAddedChange discard={discard}>
|
||||
<StrategyTooltipLink change={change}>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
feature={feature.name}
|
||||
environmentName={changeRequest.environment}
|
||||
project={changeRequest.project}
|
||||
/>
|
||||
</StrategyTooltipLink>
|
||||
</StrategyAddedChange>
|
||||
<StrategyExecution strategy={change.payload} />
|
||||
</>
|
||||
)}
|
||||
{change.action === 'deleteStrategy' && (
|
||||
<StrategyDeletedChange discard={discard}>
|
||||
{hasNameField(change.payload) && (
|
||||
<StrategyTooltipLink change={change}>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
feature={feature.name}
|
||||
environmentName={changeRequest.environment}
|
||||
project={changeRequest.project}
|
||||
/>
|
||||
</StrategyTooltipLink>
|
||||
)}
|
||||
</StrategyDeletedChange>
|
||||
)}
|
||||
{change.action === 'updateStrategy' && (
|
||||
<>
|
||||
<StrategyEditedChange discard={discard}>
|
||||
<StrategyTooltipLink change={change}>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
feature={feature.name}
|
||||
environmentName={changeRequest.environment}
|
||||
project={changeRequest.project}
|
||||
/>
|
||||
</StrategyTooltipLink>
|
||||
</StrategyEditedChange>
|
||||
<StrategyExecution strategy={change.payload} />
|
||||
</>
|
||||
)}
|
||||
{change.action === 'addStrategy' ||
|
||||
change.action === 'deleteStrategy' ||
|
||||
change.action === 'updateStrategy' ? (
|
||||
<StrategyChange
|
||||
discard={discard}
|
||||
change={change}
|
||||
featureName={feature.name}
|
||||
environmentName={changeRequest.environment}
|
||||
projectId={changeRequest.project}
|
||||
/>
|
||||
) : null}
|
||||
{change.action === 'patchVariant' && (
|
||||
<VariantPatch
|
||||
feature={feature.name}
|
||||
|
@ -1,5 +1,21 @@
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import { FC, ReactNode } from 'react';
|
||||
import { VFC, FC, ReactNode } from 'react';
|
||||
import { Box, styled, Tooltip, Typography } from '@mui/material';
|
||||
import BlockIcon from '@mui/icons-material/Block';
|
||||
import TrackChangesIcon from '@mui/icons-material/TrackChanges';
|
||||
import {
|
||||
StrategyDiff,
|
||||
StrategyTooltipLink,
|
||||
} from '../../StrategyTooltipLink/StrategyTooltipLink';
|
||||
import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
|
||||
import {
|
||||
IChangeRequestAddStrategy,
|
||||
IChangeRequestDeleteStrategy,
|
||||
IChangeRequestUpdateStrategy,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
import { useCurrentStrategy } from './hooks/useCurrentStrategy';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
|
||||
export const ChangeItemWrapper = styled(Box)({
|
||||
display: 'flex',
|
||||
@ -7,7 +23,7 @@ export const ChangeItemWrapper = styled(Box)({
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const ChangeItemCreateEditWrapper = styled(Box)(({ theme }) => ({
|
||||
const ChangeItemCreateEditWrapper = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
@ -20,55 +36,175 @@ const ChangeItemInfo: FC = styled(Box)(({ theme }) => ({
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const StrategyAddedChange: FC<{ discard?: ReactNode }> = ({
|
||||
children,
|
||||
discard,
|
||||
}) => {
|
||||
const hasNameField = (payload: unknown): payload is { name: string } =>
|
||||
typeof payload === 'object' && payload !== null && 'name' in payload;
|
||||
|
||||
const DisabledEnabledState: VFC<{ disabled: boolean }> = ({ disabled }) => {
|
||||
if (disabled) {
|
||||
return (
|
||||
<Tooltip
|
||||
title="This strategy will not be taken into account when evaluating feature toggle."
|
||||
arrow
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Badge color="disabled" icon={<BlockIcon />}>
|
||||
Disabled
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ChangeItemCreateEditWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
sx={theme => ({
|
||||
color: theme.palette.success.dark,
|
||||
})}
|
||||
>
|
||||
+ Adding strategy:
|
||||
</Typography>
|
||||
{children}
|
||||
</ChangeItemInfo>
|
||||
{discard}
|
||||
</ChangeItemCreateEditWrapper>
|
||||
<Tooltip
|
||||
title="This was disabled before and with this change it will be taken into account when evaluating feature toggle."
|
||||
arrow
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Badge color="success" icon={<TrackChangesIcon />}>
|
||||
Enabled
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const StrategyEditedChange: FC<{ discard?: ReactNode }> = ({
|
||||
children,
|
||||
discard,
|
||||
}) => {
|
||||
return (
|
||||
<ChangeItemCreateEditWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography>Editing strategy:</Typography>
|
||||
{children}
|
||||
</ChangeItemInfo>
|
||||
{discard}
|
||||
</ChangeItemCreateEditWrapper>
|
||||
);
|
||||
const EditHeader: VFC<{
|
||||
wasDisabled?: boolean;
|
||||
willBeDisabled?: boolean;
|
||||
}> = ({ wasDisabled = false, willBeDisabled = false }) => {
|
||||
if (wasDisabled && willBeDisabled) {
|
||||
return (
|
||||
<Typography color="action.disabled">
|
||||
Editing disabled strategy
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (!wasDisabled && willBeDisabled) {
|
||||
return <Typography color="error.dark">Editing strategy</Typography>;
|
||||
}
|
||||
|
||||
if (wasDisabled && !willBeDisabled) {
|
||||
return <Typography color="success.dark">Editing strategy</Typography>;
|
||||
}
|
||||
|
||||
return <Typography>Editing strategy:</Typography>;
|
||||
};
|
||||
|
||||
export const StrategyDeletedChange: FC<{ discard?: ReactNode }> = ({
|
||||
discard,
|
||||
children,
|
||||
}) => {
|
||||
export const StrategyChange: VFC<{
|
||||
discard?: ReactNode;
|
||||
change:
|
||||
| IChangeRequestAddStrategy
|
||||
| IChangeRequestDeleteStrategy
|
||||
| IChangeRequestUpdateStrategy;
|
||||
environmentName: string;
|
||||
featureName: string;
|
||||
projectId: string;
|
||||
}> = ({ discard, change, featureName, environmentName, projectId }) => {
|
||||
const currentStrategy = useCurrentStrategy(
|
||||
change,
|
||||
projectId,
|
||||
featureName,
|
||||
environmentName
|
||||
);
|
||||
|
||||
return (
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography sx={theme => ({ color: theme.palette.error.main })}>
|
||||
- Deleting strategy
|
||||
</Typography>
|
||||
{children}
|
||||
</ChangeItemInfo>
|
||||
{discard}
|
||||
</ChangeItemWrapper>
|
||||
<>
|
||||
{change.action === 'addStrategy' && (
|
||||
<>
|
||||
<ChangeItemCreateEditWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
color={
|
||||
change.payload?.disabled
|
||||
? 'action.disabled'
|
||||
: 'success.dark'
|
||||
}
|
||||
>
|
||||
+ Adding strategy:
|
||||
</Typography>
|
||||
<StrategyTooltipLink change={change}>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
currentStrategy={currentStrategy}
|
||||
/>
|
||||
</StrategyTooltipLink>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(
|
||||
change.payload?.disabled === true
|
||||
)}
|
||||
show={<DisabledEnabledState disabled={true} />}
|
||||
/>
|
||||
</ChangeItemInfo>
|
||||
{discard}
|
||||
</ChangeItemCreateEditWrapper>
|
||||
<StrategyExecution strategy={change.payload} />
|
||||
</>
|
||||
)}
|
||||
{change.action === 'deleteStrategy' && (
|
||||
<ChangeItemWrapper>
|
||||
<ChangeItemInfo>
|
||||
<Typography
|
||||
sx={theme => ({ color: theme.palette.error.main })}
|
||||
>
|
||||
- Deleting strategy
|
||||
</Typography>
|
||||
{hasNameField(change.payload) && (
|
||||
<StrategyTooltipLink change={change}>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
currentStrategy={currentStrategy}
|
||||
/>
|
||||
</StrategyTooltipLink>
|
||||
)}
|
||||
</ChangeItemInfo>
|
||||
{discard}
|
||||
</ChangeItemWrapper>
|
||||
)}
|
||||
{change.action === 'updateStrategy' && (
|
||||
<>
|
||||
<ChangeItemCreateEditWrapper>
|
||||
<ChangeItemInfo>
|
||||
<EditHeader
|
||||
wasDisabled={currentStrategy?.disabled}
|
||||
willBeDisabled={change.payload?.disabled}
|
||||
/>
|
||||
<StrategyTooltipLink
|
||||
change={change}
|
||||
previousTitle={currentStrategy?.title}
|
||||
>
|
||||
<StrategyDiff
|
||||
change={change}
|
||||
currentStrategy={currentStrategy}
|
||||
/>
|
||||
</StrategyTooltipLink>
|
||||
</ChangeItemInfo>
|
||||
{discard}
|
||||
</ChangeItemCreateEditWrapper>
|
||||
<StrategyExecution strategy={change.payload} />
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
change.payload?.disabled !==
|
||||
currentStrategy?.disabled
|
||||
}
|
||||
show={
|
||||
<Typography
|
||||
sx={{
|
||||
marginTop: theme => theme.spacing(2),
|
||||
paddingLeft: theme => theme.spacing(3),
|
||||
paddingRight: theme => theme.spacing(3),
|
||||
...flexRow,
|
||||
gap: theme => theme.spacing(1),
|
||||
}}
|
||||
>
|
||||
This strategy will be{' '}
|
||||
<DisabledEnabledState
|
||||
disabled={change.payload?.disabled || false}
|
||||
/>
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,25 @@
|
||||
import {
|
||||
IChangeRequestAddStrategy,
|
||||
IChangeRequestDeleteStrategy,
|
||||
IChangeRequestUpdateStrategy,
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
|
||||
export const useCurrentStrategy = (
|
||||
change:
|
||||
| IChangeRequestAddStrategy
|
||||
| IChangeRequestUpdateStrategy
|
||||
| IChangeRequestDeleteStrategy,
|
||||
project: string,
|
||||
feature: string,
|
||||
environmentName: string
|
||||
) => {
|
||||
const currentFeature = useFeature(project, feature);
|
||||
const currentStrategy = currentFeature.feature?.environments
|
||||
.find(environment => environment.name === environmentName)
|
||||
?.strategies.find(
|
||||
strategy =>
|
||||
'id' in change.payload && strategy.id === change.payload.id
|
||||
);
|
||||
return currentStrategy;
|
||||
};
|
@ -8,11 +8,13 @@ import {
|
||||
formatStrategyName,
|
||||
GetFeatureStrategyIcon,
|
||||
} from 'utils/strategyNames';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import EventDiff from 'component/events/EventDiff/EventDiff';
|
||||
import omit from 'lodash.omit';
|
||||
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
||||
import { styled } from '@mui/material';
|
||||
import { Typography, styled } from '@mui/material';
|
||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { textTruncated } from 'themes/themeStyles';
|
||||
|
||||
const StyledCodeSection = styled('div')(({ theme }) => ({
|
||||
overflowX: 'auto',
|
||||
@ -25,41 +27,13 @@ const StyledCodeSection = styled('div')(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const useCurrentStrategy = (
|
||||
change:
|
||||
| IChangeRequestAddStrategy
|
||||
| IChangeRequestUpdateStrategy
|
||||
| IChangeRequestDeleteStrategy,
|
||||
project: string,
|
||||
feature: string,
|
||||
environmentName: string
|
||||
) => {
|
||||
const currentFeature = useFeature(project, feature);
|
||||
const currentStrategy = currentFeature.feature?.environments
|
||||
.find(environment => environment.name === environmentName)
|
||||
?.strategies.find(
|
||||
strategy =>
|
||||
'id' in change.payload && strategy.id === change.payload.id
|
||||
);
|
||||
return currentStrategy;
|
||||
};
|
||||
|
||||
export const StrategyDiff: FC<{
|
||||
change:
|
||||
| IChangeRequestAddStrategy
|
||||
| IChangeRequestUpdateStrategy
|
||||
| IChangeRequestDeleteStrategy;
|
||||
project: string;
|
||||
feature: string;
|
||||
environmentName: string;
|
||||
}> = ({ change, project, feature, environmentName }) => {
|
||||
const currentStrategy = useCurrentStrategy(
|
||||
change,
|
||||
project,
|
||||
feature,
|
||||
environmentName
|
||||
);
|
||||
|
||||
currentStrategy?: IFeatureStrategy;
|
||||
}> = ({ change, currentStrategy }) => {
|
||||
const changeRequestStrategy =
|
||||
change.action === 'deleteStrategy' ? undefined : change.payload;
|
||||
|
||||
@ -79,14 +53,35 @@ interface IStrategyTooltipLinkProps {
|
||||
| IChangeRequestAddStrategy
|
||||
| IChangeRequestUpdateStrategy
|
||||
| IChangeRequestDeleteStrategy;
|
||||
previousTitle?: string;
|
||||
}
|
||||
|
||||
export const StrategyTooltipLink: FC<IStrategyTooltipLinkProps> = ({
|
||||
change,
|
||||
previousTitle,
|
||||
children,
|
||||
}) => (
|
||||
<>
|
||||
<GetFeatureStrategyIcon strategyName={change.payload.name} />
|
||||
<ConditionallyRender
|
||||
condition={Boolean(
|
||||
previousTitle && previousTitle !== change.payload.title
|
||||
)}
|
||||
show={
|
||||
<>
|
||||
<Typography
|
||||
component="s"
|
||||
color="action.disabled"
|
||||
sx={{
|
||||
...textTruncated,
|
||||
maxWidth: '100px',
|
||||
}}
|
||||
>
|
||||
{previousTitle}
|
||||
</Typography>{' '}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<TooltipLink
|
||||
tooltip={children}
|
||||
tooltipProps={{
|
||||
@ -94,7 +89,20 @@ export const StrategyTooltipLink: FC<IStrategyTooltipLinkProps> = ({
|
||||
maxHeight: 600,
|
||||
}}
|
||||
>
|
||||
{formatStrategyName(change.payload.name)}
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{
|
||||
...textTruncated,
|
||||
maxWidth:
|
||||
previousTitle === change.payload.title
|
||||
? '300px'
|
||||
: '200px',
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
{change.payload.title ||
|
||||
formatStrategyName(change.payload.name)}
|
||||
</Typography>
|
||||
</TooltipLink>
|
||||
</>
|
||||
);
|
||||
|
@ -106,7 +106,7 @@ type ChangeRequestEnabled = { enabled: boolean };
|
||||
|
||||
type ChangeRequestAddStrategy = Pick<
|
||||
IFeatureStrategy,
|
||||
'parameters' | 'constraints' | 'segments'
|
||||
'parameters' | 'constraints' | 'segments' | 'title' | 'disabled'
|
||||
> & { name: string };
|
||||
|
||||
type ChangeRequestEditStrategy = ChangeRequestAddStrategy & { id: string };
|
||||
@ -114,6 +114,8 @@ type ChangeRequestEditStrategy = ChangeRequestAddStrategy & { id: string };
|
||||
type ChangeRequestDeleteStrategy = {
|
||||
id: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type ChangeRequestAction =
|
||||
@ -122,6 +124,3 @@ export type ChangeRequestAction =
|
||||
| 'updateStrategy'
|
||||
| 'deleteStrategy'
|
||||
| 'patchVariant';
|
||||
|
||||
export const hasNameField = (payload: unknown): payload is { name: string } =>
|
||||
typeof payload === 'object' && payload !== null && 'name' in payload;
|
||||
|
@ -9,7 +9,14 @@ import React, {
|
||||
} from 'react';
|
||||
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
|
||||
|
||||
type Color = 'info' | 'success' | 'warning' | 'error' | 'secondary' | 'neutral';
|
||||
type Color =
|
||||
| 'info'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'error'
|
||||
| 'secondary'
|
||||
| 'neutral'
|
||||
| 'disabled'; // TODO: refactor theme
|
||||
|
||||
interface IBadgeProps {
|
||||
as?: React.ElementType;
|
||||
@ -37,16 +44,27 @@ const StyledBadge = styled('div')<IBadgeProps>(
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
lineHeight: 1,
|
||||
backgroundColor: theme.palette[color].light,
|
||||
color: theme.palette[color].contrastText,
|
||||
border: `1px solid ${theme.palette[color].border}`,
|
||||
...(color === 'disabled'
|
||||
? {
|
||||
color: theme.palette.text.secondary,
|
||||
background: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
}
|
||||
: {
|
||||
backgroundColor: theme.palette[color].light,
|
||||
color: theme.palette[color].contrastText,
|
||||
border: `1px solid ${theme.palette[color].border}`,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const StyledBadgeIcon = styled('div')<IBadgeIconProps>(
|
||||
({ theme, color = 'neutral', iconRight = false }) => ({
|
||||
display: 'flex',
|
||||
color: theme.palette[color].main,
|
||||
color:
|
||||
color === 'disabled'
|
||||
? theme.palette.action.disabled
|
||||
: theme.palette[color].main,
|
||||
margin: iconRight
|
||||
? theme.spacing(0, 0, 0, 0.5)
|
||||
: theme.spacing(0, 0.5, 0, 0),
|
||||
|
@ -29,7 +29,7 @@ interface IConstraintAccordionViewProps {
|
||||
const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: 'none',
|
||||
margin: 0,
|
||||
'&:before': {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DragEventHandler, FC, ReactNode } from 'react';
|
||||
import { DragIndicator } from '@mui/icons-material';
|
||||
import { styled, IconButton, Box } from '@mui/material';
|
||||
import { styled, IconButton, Box, Chip } from '@mui/material';
|
||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||
import {
|
||||
getFeatureStrategyIcon,
|
||||
@ -10,6 +10,7 @@ import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { PlaygroundStrategySchema } from 'openapi';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { Badge } from '../Badge/Badge';
|
||||
|
||||
interface IStrategyItemContainerProps {
|
||||
strategy: IFeatureStrategy | PlaygroundStrategySchema;
|
||||
@ -39,26 +40,35 @@ const StyledIndexLabel = styled('div')(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledContainer = styled(Box)(({ theme }) => ({
|
||||
const StyledContainer = styled(Box, {
|
||||
shouldForwardProp: prop => prop !== 'disabled',
|
||||
})<{ disabled?: boolean }>(({ theme, disabled }) => ({
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
'& + &': {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
background: theme.palette.background.paper,
|
||||
background: disabled
|
||||
? theme.palette.envAccordion.disabled
|
||||
: theme.palette.background.paper,
|
||||
}));
|
||||
|
||||
const StyledHeader = styled('div', {
|
||||
shouldForwardProp: prop => prop !== 'draggable',
|
||||
})(({ theme, draggable }) => ({
|
||||
padding: theme.spacing(0.5, 2),
|
||||
display: 'flex',
|
||||
gap: theme.spacing(1),
|
||||
alignItems: 'center',
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
paddingLeft: draggable ? theme.spacing(1) : theme.spacing(2),
|
||||
}));
|
||||
shouldForwardProp: prop => prop !== 'draggable' && prop !== 'disabled',
|
||||
})<{ draggable: boolean; disabled: boolean }>(
|
||||
({ theme, draggable, disabled }) => ({
|
||||
padding: theme.spacing(0.5, 2),
|
||||
display: 'flex',
|
||||
gap: theme.spacing(1),
|
||||
alignItems: 'center',
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
paddingLeft: draggable ? theme.spacing(1) : theme.spacing(2),
|
||||
color: disabled
|
||||
? theme.palette.action.disabled
|
||||
: theme.palette.text.primary,
|
||||
})
|
||||
);
|
||||
|
||||
export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
|
||||
strategy,
|
||||
@ -78,8 +88,14 @@ export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
|
||||
condition={orderNumber !== undefined}
|
||||
show={<StyledIndexLabel>{orderNumber}</StyledIndexLabel>}
|
||||
/>
|
||||
<StyledContainer style={style}>
|
||||
<StyledHeader draggable={Boolean(onDragStart)}>
|
||||
<StyledContainer
|
||||
disabled={strategy?.disabled || false}
|
||||
style={style}
|
||||
>
|
||||
<StyledHeader
|
||||
draggable={Boolean(onDragStart)}
|
||||
disabled={Boolean(strategy?.disabled)}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(onDragStart)}
|
||||
show={() => (
|
||||
@ -113,6 +129,14 @@ export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
|
||||
: strategy.name
|
||||
)}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(strategy?.disabled)}
|
||||
show={() => (
|
||||
<>
|
||||
<Badge color="disabled">Disabled</Badge>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: 'auto',
|
||||
|
@ -29,6 +29,52 @@ import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useCh
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
|
||||
const useTitleTracking = () => {
|
||||
const [previousTitle, setPreviousTitle] = useState<string>('');
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
|
||||
const trackTitle = (title: string = '') => {
|
||||
// don't expose the title, just if it was added, removed, or edited
|
||||
if (title === previousTitle) {
|
||||
trackEvent('strategyTitle', {
|
||||
props: {
|
||||
action: 'none',
|
||||
on: 'edit',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (previousTitle === '' && title !== '') {
|
||||
trackEvent('strategyTitle', {
|
||||
props: {
|
||||
action: 'added',
|
||||
on: 'edit',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (previousTitle !== '' && title === '') {
|
||||
trackEvent('strategyTitle', {
|
||||
props: {
|
||||
action: 'removed',
|
||||
on: 'edit',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (previousTitle !== '' && title !== '' && title !== previousTitle) {
|
||||
trackEvent('strategyTitle', {
|
||||
props: {
|
||||
action: 'edited',
|
||||
on: 'edit',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
setPreviousTitle,
|
||||
trackTitle,
|
||||
};
|
||||
};
|
||||
|
||||
export const FeatureStrategyEdit = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
@ -48,7 +94,7 @@ export const FeatureStrategyEdit = () => {
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
const { refetch: refetchChangeRequests } =
|
||||
usePendingChangeRequests(projectId);
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
const { setPreviousTitle, trackTitle } = useTitleTracking();
|
||||
|
||||
const { feature, refetchFeature } = useFeature(projectId, featureId);
|
||||
|
||||
@ -87,6 +133,7 @@ export const FeatureStrategyEdit = () => {
|
||||
.flatMap(environment => environment.strategies)
|
||||
.find(strategy => strategy.id === strategyId);
|
||||
setStrategy(prev => ({ ...prev, ...savedStrategy }));
|
||||
setPreviousTitle(savedStrategy?.title || '');
|
||||
}, [strategyId, data]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -106,12 +153,10 @@ export const FeatureStrategyEdit = () => {
|
||||
payload
|
||||
);
|
||||
|
||||
trackEvent('strategyTitle', {
|
||||
props: {
|
||||
hasTitle: Boolean(strategy.title),
|
||||
on: 'edit',
|
||||
},
|
||||
});
|
||||
if (uiConfig?.flags?.strategyTitle) {
|
||||
// NOTE: remove tracking when feature flag is removed
|
||||
trackTitle(strategy.title);
|
||||
}
|
||||
|
||||
await refetchSavedStrategySegments();
|
||||
setToastData({
|
||||
@ -202,6 +247,7 @@ export const createStrategyPayload = (
|
||||
constraints: strategy.constraints ?? [],
|
||||
parameters: strategy.parameters ?? {},
|
||||
segments: segments.map(segment => segment.id),
|
||||
disabled: strategy.disabled ?? false,
|
||||
});
|
||||
|
||||
export const formatFeaturePath = (
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { FormControlLabel, Switch } from '@mui/material';
|
||||
import { VFC } from 'react';
|
||||
|
||||
interface IFeatureStrategyEnabledDisabledProps {
|
||||
enabled: boolean;
|
||||
onToggleEnabled: () => void;
|
||||
}
|
||||
|
||||
export const FeatureStrategyEnabledDisabled: VFC<
|
||||
IFeatureStrategyEnabledDisabledProps
|
||||
> = ({ enabled, onToggleEnabled }) => {
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
name="enabled"
|
||||
onChange={onToggleEnabled}
|
||||
checked={enabled}
|
||||
/>
|
||||
}
|
||||
label="Enabled – This strategy will be used when evaluating feature toggles."
|
||||
/>
|
||||
);
|
||||
};
|
@ -30,6 +30,7 @@ import { useChangeRequestInReviewWarning } from 'hooks/useChangeRequestInReviewW
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
import { useHasProjectEnvironmentAccess } from 'hooks/useHasAccess';
|
||||
import { FeatureStrategyTitle } from './FeatureStrategyTitle/FeatureStrategyTitle';
|
||||
import { FeatureStrategyEnabledDisabled } from './FeatureStrategyEnabledDisabled/FeatureStrategyEnabledDisabled';
|
||||
|
||||
interface IFeatureStrategyFormProps {
|
||||
feature: IFeatureToggle;
|
||||
@ -250,6 +251,16 @@ export const FeatureStrategyForm = ({
|
||||
hasAccess={access}
|
||||
/>
|
||||
<StyledHr />
|
||||
<FeatureStrategyEnabledDisabled
|
||||
enabled={!strategy?.disabled}
|
||||
onToggleEnabled={() =>
|
||||
setStrategy(strategyState => ({
|
||||
...strategyState,
|
||||
disabled: !strategyState.disabled,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<StyledHr />
|
||||
<StyledButtons>
|
||||
<PermissionButton
|
||||
permission={permission}
|
||||
|
@ -0,0 +1,152 @@
|
||||
import { VFC, useState } from 'react';
|
||||
import { Alert } from '@mui/material';
|
||||
import BlockIcon from '@mui/icons-material/Block';
|
||||
import TrackChangesIcon from '@mui/icons-material/TrackChanges';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { UPDATE_FEATURE_STRATEGY } from '@server/types/permissions';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { useEnableDisable } from './hooks/useEnableDisable';
|
||||
import { useSuggestEnableDisable } from './hooks/useSuggestEnableDisable';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { FeatureStrategyChangeRequestAlert } from 'component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyChangeRequestAlert/FeatureStrategyChangeRequestAlert';
|
||||
import { IDisableEnableStrategyProps } from './IDisableEnableStrategyProps';
|
||||
|
||||
const DisableStrategy: VFC<IDisableEnableStrategyProps> = ({ ...props }) => {
|
||||
const { projectId, environmentId } = props;
|
||||
const [isDialogueOpen, setDialogueOpen] = useState(false);
|
||||
const { onDisable } = useEnableDisable({ ...props });
|
||||
const { onSuggestDisable } = useSuggestEnableDisable({ ...props });
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
const isChangeRequest = isChangeRequestConfigured(environmentId);
|
||||
|
||||
const onClick = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (isChangeRequest) {
|
||||
onSuggestDisable();
|
||||
} else {
|
||||
onDisable();
|
||||
}
|
||||
setDialogueOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PermissionIconButton
|
||||
onClick={() => setDialogueOpen(true)}
|
||||
projectId={projectId}
|
||||
environmentId={environmentId}
|
||||
permission={UPDATE_FEATURE_STRATEGY}
|
||||
tooltipProps={{
|
||||
title: 'Disable strategy',
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<BlockIcon />
|
||||
</PermissionIconButton>
|
||||
<Dialogue
|
||||
title={
|
||||
isChangeRequest
|
||||
? 'Add disabling strategy to change request?'
|
||||
: 'Are you sure you want to disable this strategy?'
|
||||
}
|
||||
open={isDialogueOpen}
|
||||
primaryButtonText={
|
||||
isChangeRequest ? 'Add to draft' : 'Disable strategy'
|
||||
}
|
||||
secondaryButtonText="Cancel"
|
||||
onClick={onClick}
|
||||
onClose={() => setDialogueOpen(false)}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={isChangeRequest}
|
||||
show={
|
||||
<FeatureStrategyChangeRequestAlert
|
||||
environment={environmentId}
|
||||
/>
|
||||
}
|
||||
elseShow={
|
||||
<Alert severity="error">
|
||||
Disabling the strategy will change which users
|
||||
receive access to the feature.
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
</Dialogue>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const EnableStrategy: VFC<IDisableEnableStrategyProps> = ({ ...props }) => {
|
||||
const { projectId, environmentId } = props;
|
||||
const [isDialogueOpen, setDialogueOpen] = useState(false);
|
||||
const { onEnable } = useEnableDisable({ ...props });
|
||||
const { onSuggestEnable } = useSuggestEnableDisable({ ...props });
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
const isChangeRequest = isChangeRequestConfigured(environmentId);
|
||||
|
||||
const onClick = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (isChangeRequest) {
|
||||
onSuggestEnable();
|
||||
} else {
|
||||
onEnable();
|
||||
}
|
||||
setDialogueOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PermissionIconButton
|
||||
onClick={() => setDialogueOpen(true)}
|
||||
projectId={projectId}
|
||||
environmentId={environmentId}
|
||||
permission={UPDATE_FEATURE_STRATEGY}
|
||||
tooltipProps={{
|
||||
title: 'Enable strategy',
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<TrackChangesIcon />
|
||||
</PermissionIconButton>
|
||||
<Dialogue
|
||||
title={
|
||||
isChangeRequest
|
||||
? 'Add enabling strategy to change request?'
|
||||
: 'Are you sure you want to enable this strategy?'
|
||||
}
|
||||
open={isDialogueOpen}
|
||||
primaryButtonText={
|
||||
isChangeRequest ? 'Add to draft' : 'Enable strategy'
|
||||
}
|
||||
secondaryButtonText="Cancel"
|
||||
onClick={onClick}
|
||||
onClose={() => setDialogueOpen(false)}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={isChangeRequest}
|
||||
show={
|
||||
<FeatureStrategyChangeRequestAlert
|
||||
environment={environmentId}
|
||||
/>
|
||||
}
|
||||
elseShow={
|
||||
<Alert severity="error">
|
||||
Enabling the strategy will change which users
|
||||
receive access to the feature.
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
</Dialogue>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DisableEnableStrategy: VFC<IDisableEnableStrategyProps> = ({
|
||||
...props
|
||||
}) =>
|
||||
props.strategy.disabled ? (
|
||||
<EnableStrategy {...props} />
|
||||
) : (
|
||||
<DisableStrategy {...props} />
|
||||
);
|
@ -0,0 +1,8 @@
|
||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
||||
|
||||
export interface IDisableEnableStrategyProps {
|
||||
projectId: string;
|
||||
featureId: string;
|
||||
environmentId: string;
|
||||
strategy: IFeatureStrategy;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { IDisableEnableStrategyProps } from '../IDisableEnableStrategyProps';
|
||||
|
||||
export const useEnableDisable = ({
|
||||
projectId,
|
||||
environmentId,
|
||||
featureId,
|
||||
strategy,
|
||||
}: IDisableEnableStrategyProps) => {
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
const { setStrategyDisabledState } = useFeatureStrategyApi();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
|
||||
const onEnableDisable = (enabled: boolean) => async () => {
|
||||
try {
|
||||
await setStrategyDisabledState(
|
||||
projectId,
|
||||
featureId,
|
||||
environmentId,
|
||||
strategy.id,
|
||||
!enabled
|
||||
);
|
||||
setToastData({
|
||||
title: `Strategy ${enabled ? 'enabled' : 'disabled'}`,
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
refetchFeature();
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
onDisable: onEnableDisable(false),
|
||||
onEnable: onEnableDisable(true),
|
||||
};
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { IDisableEnableStrategyProps } from '../IDisableEnableStrategyProps';
|
||||
|
||||
export const useSuggestEnableDisable = ({
|
||||
projectId,
|
||||
environmentId,
|
||||
featureId,
|
||||
strategy,
|
||||
}: IDisableEnableStrategyProps) => {
|
||||
const { addChange } = useChangeRequestApi();
|
||||
const { refetch: refetchChangeRequests } =
|
||||
usePendingChangeRequests(projectId);
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const onSuggestEnableDisable = (enabled: boolean) => async () => {
|
||||
try {
|
||||
await addChange(projectId, environmentId, {
|
||||
action: 'updateStrategy',
|
||||
feature: featureId,
|
||||
payload: {
|
||||
...strategy,
|
||||
disabled: !enabled,
|
||||
},
|
||||
});
|
||||
setToastData({
|
||||
title: 'Changes added to the draft!',
|
||||
type: 'success',
|
||||
});
|
||||
await refetchChangeRequests();
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
return {
|
||||
onSuggestDisable: onSuggestEnableDisable(false),
|
||||
onSuggestEnable: onSuggestEnableDisable(true),
|
||||
};
|
||||
};
|
@ -12,6 +12,8 @@ import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
|
||||
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
||||
import { DisableEnableStrategy } from './DisableEnableStrategy/DisableEnableStrategy';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
interface IStrategyItemProps {
|
||||
environmentId: string;
|
||||
@ -32,6 +34,7 @@ export const StrategyItem: FC<IStrategyItemProps> = ({
|
||||
orderNumber,
|
||||
headerChildren,
|
||||
}) => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
|
||||
@ -76,6 +79,17 @@ export const StrategyItem: FC<IStrategyItemProps> = ({
|
||||
>
|
||||
<Edit />
|
||||
</PermissionIconButton>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(uiConfig?.flags?.strategyDisable)}
|
||||
show={() => (
|
||||
<DisableEnableStrategy
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environmentId}
|
||||
strategy={strategy}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<FeatureStrategyRemove
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
|
@ -111,7 +111,7 @@ export const ChangeRequestTable: VFC = () => {
|
||||
return {
|
||||
key,
|
||||
label: `${key} ${labelText}`,
|
||||
sx: { 'font-size': theme.fontSizes.smallBody },
|
||||
sx: { fontSize: theme.fontSizes.smallBody },
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -71,11 +71,37 @@ const useFeatureStrategyApi = () => {
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const setStrategyDisabledState = async (
|
||||
projectId: string,
|
||||
featureId: string,
|
||||
environmentId: string,
|
||||
strategyId: string,
|
||||
disabled: boolean
|
||||
): Promise<void> => {
|
||||
const path = `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/strategies/${strategyId}`;
|
||||
const req = createRequest(
|
||||
path,
|
||||
{
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify([
|
||||
{
|
||||
path: '/disabled',
|
||||
value: disabled,
|
||||
op: 'replace',
|
||||
},
|
||||
]),
|
||||
},
|
||||
'setStrategyDisabledState'
|
||||
);
|
||||
await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
return {
|
||||
addStrategyToFeature,
|
||||
updateStrategyOnFeature,
|
||||
deleteStrategyFromFeature,
|
||||
setStrategiesSortOrder,
|
||||
setStrategyDisabledState,
|
||||
loading,
|
||||
errors,
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ export interface IFeatureStrategy {
|
||||
projectId?: string;
|
||||
environment?: string;
|
||||
segments?: number[];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface IFeatureStrategyParameters {
|
||||
@ -24,6 +25,7 @@ export interface IFeatureStrategyPayload {
|
||||
constraints: IConstraint[];
|
||||
parameters: IFeatureStrategyParameters;
|
||||
segments?: number[];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface IStrategy {
|
||||
|
@ -51,6 +51,7 @@ export interface IFlags {
|
||||
demo?: boolean;
|
||||
strategyTitle?: boolean;
|
||||
groupRootRoles?: boolean;
|
||||
strategyDisable?: boolean;
|
||||
googleAuthEnabled?: boolean;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,8 @@ export interface CreateFeatureStrategySchema {
|
||||
name: string;
|
||||
/** A descriptive title for the strategy */
|
||||
title?: string | null;
|
||||
/** A toggle to disable the strategy. defaults to false. Disabled strategies are not evaluated or returned to the SDKs */
|
||||
disabled?: boolean | null;
|
||||
/** The order of the strategy in the list */
|
||||
sortOrder?: number;
|
||||
/** A list of the constraints attached to the strategy */
|
||||
|
@ -16,6 +16,8 @@ export interface FeatureStrategySchema {
|
||||
name: string;
|
||||
/** A descriptive title for the strategy */
|
||||
title?: string | null;
|
||||
/** A toggle to disable the strategy. defaults to false. Disabled strategies are not evaluated or returned to the SDKs */
|
||||
disabled?: boolean | null;
|
||||
/** The name or feature the strategy is attached to */
|
||||
featureName?: string;
|
||||
/** The order of the strategy in the list */
|
||||
|
@ -10,6 +10,8 @@ export interface GroupSchema {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
mappingsSSO?: string[];
|
||||
/** A role id that is used as the root role for all users in this group. This can be either the id of the Editor or Admin role. */
|
||||
rootRole?: number | null;
|
||||
createdBy?: string | null;
|
||||
createdAt?: string | null;
|
||||
users?: GroupUserModelSchema[];
|
||||
|
@ -17,6 +17,8 @@ export interface PlaygroundStrategySchema {
|
||||
id: string;
|
||||
/** The strategy's evaluation result. If the strategy is a custom strategy that Unleash can't evaluate, `evaluationStatus` will be `unknown`. Otherwise, it will be `true` or `false` */
|
||||
result: PlaygroundStrategySchemaResult;
|
||||
/** The strategy's status. Disabled strategies are not evaluated */
|
||||
disabled: boolean | null;
|
||||
/** The strategy's segments and their evaluation results. */
|
||||
segments: PlaygroundSegmentSchema[];
|
||||
/** The strategy's constraints and their evaluation results. */
|
||||
|
@ -15,6 +15,10 @@ import type { EnvironmentSchema } from './environmentSchema';
|
||||
import type { SegmentSchema } from './segmentSchema';
|
||||
import type { FeatureStrategySegmentSchema } from './featureStrategySegmentSchema';
|
||||
|
||||
/**
|
||||
* The state of the application used by export/import APIs which are deprecated in favor of the more fine grained /api/admin/export and /api/admin/import APIs
|
||||
* @deprecated
|
||||
*/
|
||||
export interface StateSchema {
|
||||
version: number;
|
||||
features?: FeatureSchema[];
|
||||
|
@ -10,5 +10,9 @@ export interface UpdateFeatureStrategySchema {
|
||||
name?: string;
|
||||
sortOrder?: number;
|
||||
constraints?: ConstraintSchema[];
|
||||
/** A descriptive title for the strategy */
|
||||
title?: string | null;
|
||||
/** A toggle to disable the strategy. defaults to true. Disabled strategies are not evaluated or returned to the SDKs */
|
||||
disabled?: boolean | null;
|
||||
parameters?: ParametersSchema;
|
||||
}
|
||||
|
@ -87,6 +87,7 @@ exports[`should create default config 1`] = `
|
||||
"proPlanAutoCharge": false,
|
||||
"projectScopedStickiness": false,
|
||||
"responseTimeWithAppNameKillSwitch": false,
|
||||
"strategyDisable": false,
|
||||
"strategyTitle": false,
|
||||
"strictSchemaValidation": false,
|
||||
},
|
||||
@ -114,6 +115,7 @@ exports[`should create default config 1`] = `
|
||||
"proPlanAutoCharge": false,
|
||||
"projectScopedStickiness": false,
|
||||
"responseTimeWithAppNameKillSwitch": false,
|
||||
"strategyDisable": false,
|
||||
"strategyTitle": false,
|
||||
"strictSchemaValidation": false,
|
||||
},
|
||||
|
@ -80,6 +80,10 @@ const flags = {
|
||||
process.env.UNLEASH_STRATEGY_TITLE,
|
||||
false,
|
||||
),
|
||||
strategyDisable: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_STRATEGY_DISABLE,
|
||||
false,
|
||||
),
|
||||
googleAuthEnabled: parseEnvVarBoolean(
|
||||
process.env.GOOGLE_AUTH_ENABLED,
|
||||
false,
|
||||
|
Loading…
Reference in New Issue
Block a user