1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-31 13:47:02 +02:00

Propagate to use IConstraintWithId everywhere until editor stops complaining.

This now passes checks to be "valid", but it doesn't do anything in and of itself. We still need to actually provide an id for these things to matter.

I'm wondering whether it'd be better to make id mandatory on the regular constraints and add it on every incoming constraint.
This commit is contained in:
Thomas Heartman 2025-07-18 13:06:59 +02:00
parent 4c358c88df
commit 6ff20cc7ff
No known key found for this signature in database
GPG Key ID: BD1F880DAED1EE78
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

@ -68,6 +68,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;
} }