1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-24 17:51:14 +02:00
This commit is contained in:
Thomas Heartman 2025-09-24 14:23:38 +02:00 committed by GitHub
commit 0def4cb3b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 49 additions and 47 deletions

View File

@ -4,7 +4,7 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import type { IFeatureStrategy } from 'interfaces/strategy'; import type { IEditableStrategy, IFeatureStrategy } from 'interfaces/strategy';
import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
import type { ISegment } from 'interfaces/segment'; import type { ISegment } from 'interfaces/segment';
import { useFormErrors } from 'hooks/useFormErrors'; import { useFormErrors } from 'hooks/useFormErrors';
@ -63,7 +63,7 @@ export const EditChange = ({
const constraintsWithId = addIdSymbolToConstraints(change.payload); const constraintsWithId = addIdSymbolToConstraints(change.payload);
const [strategy, setStrategy] = useState<Partial<IFeatureStrategy>>({ const [strategy, setStrategy] = useState<Partial<IEditableStrategy>>({
...change.payload, ...change.payload,
constraints: constraintsWithId, constraints: constraintsWithId,
}); });

View File

@ -7,12 +7,12 @@ import {
type Theme, type Theme,
styled, styled,
} from '@mui/material'; } from '@mui/material';
import type { IConstraint } from 'interfaces/strategy';
import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx'; import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx';
import { ConstraintAccordionViewHeader } from './ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx'; import { ConstraintAccordionViewHeader } from './ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx';
import type { IConstraintWithId } from 'interfaces/strategy.ts';
interface IConstraintAccordionViewProps { interface IConstraintAccordionViewProps {
constraint: IConstraint; constraint: IConstraintWithId;
onUse?: () => void; onUse?: () => void;
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
disabled?: boolean; disabled?: boolean;

View File

@ -1,21 +1,20 @@
import type React from 'react'; import type React from 'react';
import { useEffect, useImperativeHandle } from 'react'; import { useImperativeHandle } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import type { IConstraint } from 'interfaces/strategy'; import type { IConstraint, IConstraintWithId } from 'interfaces/strategy';
import produce from 'immer'; import produce from 'immer';
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
import { ConstraintsList } from 'component/common/ConstraintsList/ConstraintsList'; import { ConstraintsList } from 'component/common/ConstraintsList/ConstraintsList';
import { EditableConstraint } from 'component/feature/FeatureStrategy/FeatureStrategyConstraints/EditableConstraint/EditableConstraint'; import { EditableConstraint } from 'component/feature/FeatureStrategy/FeatureStrategyConstraints/EditableConstraint/EditableConstraint';
import { createEmptyConstraint } from '../../../../utils/createEmptyConstraint.ts'; import { createEmptyConstraint } from '../../../../utils/createEmptyConstraint.ts';
import { constraintId } from 'constants/constraintId.ts'; import { constraintId } from 'constants/constraintId.ts';
import { v4 as uuidv4 } from 'uuid';
export interface IEditableConstraintsListRef { export interface IEditableConstraintsListRef {
addConstraint?: (contextName: string) => void; addConstraint?: (contextName: string) => void;
} }
export interface IEditableConstraintsListProps { export interface IEditableConstraintsListProps {
constraints: IConstraint[]; constraints: IConstraintWithId[];
setConstraints: React.Dispatch<React.SetStateAction<IConstraint[]>>; setConstraints: React.Dispatch<React.SetStateAction<IConstraint[]>>;
} }
@ -40,17 +39,6 @@ export const EditableConstraintsList = forwardRef<
}, },
})); }));
useEffect(() => {
if (!constraints.every((constraint) => constraintId in constraint)) {
setConstraints(
constraints.map((constraint) => ({
[constraintId]: uuidv4(),
...constraint,
})),
);
}
}, [constraints, setConstraints]);
const onDelete = (index: number) => { const onDelete = (index: number) => {
setConstraints( setConstraints(
produce((draft) => { produce((draft) => {
@ -79,6 +67,7 @@ export const EditableConstraintsList = forwardRef<
return ( return (
<StyledContainer> <StyledContainer>
IN THE constraints list. mapping: {JSON.stringify(constraints)}
<ConstraintsList> <ConstraintsList>
{constraints.map((constraint, index) => ( {constraints.map((constraint, index) => (
<EditableConstraint <EditableConstraint

View File

@ -2,7 +2,7 @@ import type React from 'react';
import { forwardRef, useImperativeHandle, type RefObject } from 'react'; import { forwardRef, useImperativeHandle, type RefObject } from 'react';
import { Box, Button, styled, Typography } from '@mui/material'; import { Box, Button, styled, Typography } from '@mui/material';
import Add from '@mui/icons-material/Add'; import Add from '@mui/icons-material/Add';
import type { IConstraint } from 'interfaces/strategy'; import type { IConstraint, IConstraintWithId } from 'interfaces/strategy';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
@ -15,8 +15,8 @@ import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashCon
import { createEmptyConstraint } from 'utils/createEmptyConstraint.ts'; import { createEmptyConstraint } from 'utils/createEmptyConstraint.ts';
interface IConstraintAccordionListProps { interface IConstraintAccordionListProps {
constraints: IConstraint[]; constraints: IConstraintWithId[];
setConstraints?: React.Dispatch<React.SetStateAction<IConstraint[]>>; setConstraints?: React.Dispatch<React.SetStateAction<IConstraintWithId[]>>;
showCreateButton?: boolean; showCreateButton?: boolean;
} }
@ -57,7 +57,7 @@ interface IConstraintAccordionListItemState {
const useConstraintAccordionList = ( const useConstraintAccordionList = (
setConstraints: setConstraints:
| React.Dispatch<React.SetStateAction<IConstraint[]>> | React.Dispatch<React.SetStateAction<IConstraintWithId[]>>
| undefined, | undefined,
ref: React.RefObject<IConstraintAccordionListRef>, ref: React.RefObject<IConstraintAccordionListRef>,
) => { ) => {
@ -88,7 +88,7 @@ const useConstraintAccordionList = (
export const FeatureStrategyConstraintAccordionList = forwardRef< export const FeatureStrategyConstraintAccordionList = forwardRef<
IConstraintAccordionListRef | undefined, IConstraintAccordionListRef | undefined,
IConstraintAccordionListProps IConstraintAccordionListProps
>(({ constraints, setConstraints, showCreateButton }, ref) => { >(({ constraints, setConstraints }, ref) => {
const { onAdd, context } = useConstraintAccordionList( const { onAdd, context } = useConstraintAccordionList(
setConstraints, setConstraints,
ref as RefObject<IConstraintAccordionListRef>, ref as RefObject<IConstraintAccordionListRef>,
@ -101,8 +101,9 @@ export const FeatureStrategyConstraintAccordionList = forwardRef<
return ( return (
<StyledContainer> <StyledContainer>
constraints are {JSON.stringify(constraints)}
<ConditionallyRender <ConditionallyRender
condition={Boolean(showCreateButton && onAdd)} condition={Boolean(true)}
show={ show={
<div> <div>
<StyledHelpIconBox> <StyledHelpIconBox>
@ -128,13 +129,14 @@ export const FeatureStrategyConstraintAccordionList = forwardRef<
} }
/> />
</StyledHelpIconBox> </StyledHelpIconBox>
{setConstraints ? (
<fieldset disabled>
<EditableConstraintsList <EditableConstraintsList
ref={ref} ref={ref}
setConstraints={setConstraints} setConstraints={() => {}}
constraints={constraints} constraints={constraints}
/> />
) : null} </fieldset>
<Box <Box
sx={(theme) => ({ sx={(theme) => ({
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
@ -156,7 +158,7 @@ export const FeatureStrategyConstraintAccordionList = forwardRef<
variant='outlined' variant='outlined'
color='primary' color='primary'
data-testid='ADD_CONSTRAINT_BUTTON' data-testid='ADD_CONSTRAINT_BUTTON'
disabled={Boolean(limitReached)} disabled={Boolean(limitReached || !onAdd)}
> >
Add constraint Add constraint
</Button> </Button>

View File

@ -1,4 +1,4 @@
import type { IConstraint, IFeatureStrategy } from 'interfaces/strategy'; import type { IConstraintWithId, IEditableStrategy } from 'interfaces/strategy';
import type React from 'react'; import type React from 'react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { import {
@ -11,9 +11,9 @@ import { FeatureStrategyConstraintAccordionList } from './FeatureStrategyConstra
interface IFeatureStrategyConstraintsProps { interface IFeatureStrategyConstraintsProps {
projectId: string; projectId: string;
environmentId: string; environmentId: string;
strategy: Partial<IFeatureStrategy>; strategy: Partial<IEditableStrategy>;
setStrategy: React.Dispatch< setStrategy: React.Dispatch<
React.SetStateAction<Partial<IFeatureStrategy>> React.SetStateAction<Partial<IEditableStrategy>>
>; >;
} }
@ -53,7 +53,9 @@ export const FeatureStrategyConstraints = ({
const constraints = strategy.constraints || []; const constraints = strategy.constraints || [];
const setConstraints = (value: React.SetStateAction<IConstraint[]>) => { const setConstraints = (
value: React.SetStateAction<IConstraintWithId[]>,
) => {
setStrategy((prev) => { setStrategy((prev) => {
return { return {
...prev, ...prev,

View File

@ -5,11 +5,11 @@ import {
areConstraintsEqual, areConstraintsEqual,
getConstraintKey, getConstraintKey,
} from './useRecentlyUsedConstraints.ts'; } from './useRecentlyUsedConstraints.ts';
import type { IConstraint } from 'interfaces/strategy'; import type { IConstraintWithId } from 'interfaces/strategy.ts';
type IRecentlyUsedConstraintsProps = { type IRecentlyUsedConstraintsProps = {
setConstraints?: React.Dispatch<React.SetStateAction<IConstraint[]>>; setConstraints?: React.Dispatch<React.SetStateAction<IConstraintWithId[]>>;
constraints?: IConstraint[]; constraints?: IConstraintWithId[];
}; };
const StyledContainer = styled('div')(({ theme }) => ({ const StyledContainer = styled('div')(({ theme }) => ({

View File

@ -1,5 +1,5 @@
import { useLocalStorageState } from 'hooks/useLocalStorageState'; import { useLocalStorageState } from 'hooks/useLocalStorageState';
import type { IConstraint } from 'interfaces/strategy'; import type { IConstraint, IConstraintWithId } from 'interfaces/strategy';
const hashString = (str: string): number => { const hashString = (str: string): number => {
let hash = 0; let hash = 0;
@ -41,14 +41,14 @@ export const areConstraintsEqual = (
}; };
export const useRecentlyUsedConstraints = ( export const useRecentlyUsedConstraints = (
initialItems: IConstraint[] = [], initialItems: IConstraintWithId[] = [],
) => { ) => {
const [items, setItems] = useLocalStorageState<IConstraint[]>( const [items, setItems] = useLocalStorageState<IConstraintWithId[]>(
'recently-used-constraints', 'recently-used-constraints',
initialItems, initialItems,
); );
const addItem = (newItem: IConstraint | IConstraint[]) => { const addItem = (newItem: IConstraintWithId | IConstraintWithId[]) => {
setItems((prevItems) => { setItems((prevItems) => {
const itemsToAdd = Array.isArray(newItem) ? newItem : [newItem]; const itemsToAdd = Array.isArray(newItem) ? newItem : [newItem];

View File

@ -13,7 +13,7 @@ import {
Link, Link,
} from '@mui/material'; } from '@mui/material';
import type { import type {
IFeatureStrategy, IEditableStrategy,
IFeatureStrategyParameters, IFeatureStrategyParameters,
IStrategyParameter, IStrategyParameter,
} from 'interfaces/strategy'; } from 'interfaces/strategy';
@ -57,9 +57,9 @@ interface IFeatureStrategyFormProps {
onCancel?: () => void; onCancel?: () => void;
loading: boolean; loading: boolean;
isChangeRequest: boolean; isChangeRequest: boolean;
strategy: Partial<IFeatureStrategy>; strategy: Partial<IEditableStrategy>;
setStrategy: React.Dispatch< setStrategy: React.Dispatch<
React.SetStateAction<Partial<IFeatureStrategy>> React.SetStateAction<Partial<IEditableStrategy>>
>; >;
segments: ISegment[]; segments: ISegment[];
setSegments: React.Dispatch<React.SetStateAction<ISegment[]>>; setSegments: React.Dispatch<React.SetStateAction<ISegment[]>>;

View File

@ -1,4 +1,8 @@
import type { IFeatureStrategy, IStrategy } from 'interfaces/strategy'; import type {
IEditableStrategy,
IFeatureStrategy,
IStrategy,
} from 'interfaces/strategy';
import DefaultStrategy from 'component/feature/StrategyTypes/DefaultStrategy/DefaultStrategy'; import DefaultStrategy from 'component/feature/StrategyTypes/DefaultStrategy/DefaultStrategy';
import FlexibleStrategy from 'component/feature/StrategyTypes/FlexibleStrategy/FlexibleStrategy'; import FlexibleStrategy from 'component/feature/StrategyTypes/FlexibleStrategy/FlexibleStrategy';
import GeneralStrategy from 'component/feature/StrategyTypes/GeneralStrategy/GeneralStrategy'; import GeneralStrategy from 'component/feature/StrategyTypes/GeneralStrategy/GeneralStrategy';
@ -12,7 +16,7 @@ interface IFeatureStrategyTypeProps {
strategy: Partial<IFeatureStrategy>; strategy: Partial<IFeatureStrategy>;
strategyDefinition: IStrategy; strategyDefinition: IStrategy;
setStrategy: React.Dispatch< setStrategy: React.Dispatch<
React.SetStateAction<Partial<IFeatureStrategy>> React.SetStateAction<Partial<IEditableStrategy>>
>; >;
validateParameter: (name: string, value: string) => boolean; validateParameter: (name: string, value: string) => boolean;
errors: IFormErrors; errors: IFormErrors;

View File

@ -8,7 +8,7 @@ import { UPDATE_FEATURE_ENVIRONMENT_VARIANTS } from '../../providers/AccessProvi
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { WeightType } from '../../../constants/variantTypes.ts'; import { WeightType } from '../../../constants/variantTypes.ts';
import { Box, styled, Typography, useTheme, Alert } from '@mui/material'; import { Box, styled, Typography, useTheme, Alert } from '@mui/material';
import type { IFeatureStrategy } from 'interfaces/strategy'; import type { IEditableStrategy, IFeatureStrategy } from 'interfaces/strategy';
import { VariantsSplitPreview } from 'component/common/VariantsSplitPreview/VariantsSplitPreview'; import { VariantsSplitPreview } from 'component/common/VariantsSplitPreview/VariantsSplitPreview';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
import { StrategyVariantsUpgradeAlert } from 'component/common/StrategyVariantsUpgradeAlert/StrategyVariantsUpgradeAlert'; import { StrategyVariantsUpgradeAlert } from 'component/common/StrategyVariantsUpgradeAlert/StrategyVariantsUpgradeAlert';
@ -30,7 +30,7 @@ const StyledHelpIconBox = styled(Box)(({ theme }) => ({
export const NewStrategyVariants: FC<{ export const NewStrategyVariants: FC<{
setStrategy: React.Dispatch< setStrategy: React.Dispatch<
React.SetStateAction<Partial<IFeatureStrategy>> React.SetStateAction<Partial<IEditableStrategy>>
>; >;
strategy: Partial<IFeatureStrategy>; strategy: Partial<IFeatureStrategy>;
projectId: string; projectId: string;

View File

@ -3,6 +3,7 @@ import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler.js'; import handleErrorResponses from '../httpErrorResponseHandler.js';
import type { ChangeRequestType } from 'component/changeRequest/changeRequest.types'; import type { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
// we get constraints here
export const useChangeRequest = (projectId: string, id: string) => { export const useChangeRequest = (projectId: string, id: string) => {
const { data, error, mutate } = useSWR<ChangeRequestType>( const { data, error, mutate } = useSWR<ChangeRequestType>(
formatApiPath(`api/admin/projects/${projectId}/change-requests/${id}`), formatApiPath(`api/admin/projects/${projectId}/change-requests/${id}`),

View File

@ -69,6 +69,10 @@ export interface IConstraint {
[constraintId]?: string; [constraintId]?: string;
} }
export interface IEditableStrategy extends IFeatureStrategy {
constraints: IConstraintWithId[];
}
export interface IConstraintWithId extends IConstraint { export interface IConstraintWithId extends IConstraint {
[constraintId]: string; [constraintId]: string;
} }