1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-08 01:15:49 +02:00

fix: add symbols as constraint ids (#5913)

This PR adds uuids as ids using a symbol in order to make sure we only
use this to keep internal order in the viritual DOM. This makes us able
to have predictable mutable lists on the frontend, and makes it easy to
not pass this property along to the backend.
This commit is contained in:
Fredrik Strand Oseberg 2024-01-16 13:47:04 +01:00 committed by GitHub
parent af4c3a86d1
commit 967ee13e62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 77 additions and 32 deletions

View File

@ -16,6 +16,8 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { comparisonModerator } from 'component/feature/FeatureStrategy/featureStrategy.utils'; import { comparisonModerator } from 'component/feature/FeatureStrategy/featureStrategy.utils';
import { import {
ChangeRequestAddStrategy,
ChangeRequestEditStrategy,
IChangeRequestAddStrategy, IChangeRequestAddStrategy,
IChangeRequestUpdateStrategy, IChangeRequestUpdateStrategy,
} from 'component/changeRequest/changeRequest.types'; } from 'component/changeRequest/changeRequest.types';
@ -25,6 +27,8 @@ import { useUiFlag } from 'hooks/useUiFlag';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm'; import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm';
import { NewStrategyVariants } from 'component/feature/StrategyTypes/NewStrategyVariants'; import { NewStrategyVariants } from 'component/feature/StrategyTypes/NewStrategyVariants';
import { constraintId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
import { v4 as uuidv4 } from 'uuid';
interface IEditChangeProps { interface IEditChangeProps {
change: IChangeRequestAddStrategy | IChangeRequestUpdateStrategy; change: IChangeRequestAddStrategy | IChangeRequestUpdateStrategy;
@ -36,6 +40,16 @@ interface IEditChangeProps {
onClose: () => void; onClose: () => void;
} }
const addIdSymbolToConstraints = (
strategy?: ChangeRequestAddStrategy | ChangeRequestEditStrategy,
) => {
if (!strategy) return;
return strategy?.constraints.map((constraint) => {
return { ...constraint, [constraintId]: uuidv4() };
});
};
export const NewEditChange = ({ export const NewEditChange = ({
change, change,
changeRequestId, changeRequestId,
@ -50,9 +64,12 @@ export const NewEditChange = ({
const [tab, setTab] = useState(0); const [tab, setTab] = useState(0);
const newStrategyConfiguration = useUiFlag('newStrategyConfiguration'); const newStrategyConfiguration = useUiFlag('newStrategyConfiguration');
const [strategy, setStrategy] = useState<Partial<IFeatureStrategy>>( const constraintsWithId = addIdSymbolToConstraints(change.payload);
change.payload,
); const [strategy, setStrategy] = useState<Partial<IFeatureStrategy>>({
...change.payload,
constraints: constraintsWithId,
});
const { segments: allSegments } = useSegments(); const { segments: allSegments } = useSegments();
const strategySegments = (allSegments || []).filter((segment) => { const strategySegments = (allSegments || []).filter((segment) => {

View File

@ -216,7 +216,7 @@ type ChangeRequestEnabled = { enabled: boolean };
type ChangeRequestAddDependency = { feature: string }; type ChangeRequestAddDependency = { feature: string };
type ChangeRequestAddStrategy = Pick< export type ChangeRequestAddStrategy = Pick<
IFeatureStrategy, IFeatureStrategy,
| 'parameters' | 'parameters'
| 'constraints' | 'constraints'
@ -226,7 +226,9 @@ type ChangeRequestAddStrategy = Pick<
| 'variants' | 'variants'
> & { name: string }; > & { name: string };
type ChangeRequestEditStrategy = ChangeRequestAddStrategy & { id: string }; export type ChangeRequestEditStrategy = ChangeRequestAddStrategy & {
id: string;
};
type ChangeRequestDeleteStrategy = { type ChangeRequestDeleteStrategy = {
id: string; id: string;

View File

@ -2,6 +2,9 @@ import { dateOperators } from 'constants/operators';
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } from 'interfaces/strategy';
import { oneOf } from 'utils/oneOf'; import { oneOf } from 'utils/oneOf';
import { operatorsForContext } from 'utils/operatorsForContext'; import { operatorsForContext } from 'utils/operatorsForContext';
import { v4 as uuidv4 } from 'uuid';
export const constraintId = Symbol('id');
export const createEmptyConstraint = (contextName: string): IConstraint => { export const createEmptyConstraint = (contextName: string): IConstraint => {
const operator = operatorsForContext(contextName)[0]; const operator = operatorsForContext(contextName)[0];
@ -17,5 +20,6 @@ export const createEmptyConstraint = (contextName: string): IConstraint => {
values: [], values: [],
caseInsensitive: false, caseInsensitive: false,
inverted: false, inverted: false,
[constraintId]: uuidv4(),
}; };
}; };

View File

@ -1,16 +1,14 @@
import React, { import React, { forwardRef, Fragment, useImperativeHandle } from 'react';
forwardRef, import { styled, Tooltip } from '@mui/material';
Fragment,
RefObject,
useImperativeHandle,
} from 'react';
import { Button, styled, Tooltip } from '@mui/material';
import { HelpOutline } from '@mui/icons-material'; import { HelpOutline } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } 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 { IUseWeakMap, useWeakMap } from 'hooks/useWeakMap'; import { IUseWeakMap, useWeakMap } from 'hooks/useWeakMap';
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint'; import {
constraintId,
createEmptyConstraint,
} from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator'; import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import { NewConstraintAccordion } from 'component/common/NewConstraintAccordion/NewConstraintAccordion'; import { NewConstraintAccordion } from 'component/common/NewConstraintAccordion/NewConstraintAccordion';
@ -162,9 +160,12 @@ export const NewConstraintAccordionList = forwardRef<
return ( return (
<StyledContainer id={constraintAccordionListId}> <StyledContainer id={constraintAccordionListId}>
{constraints.map((constraint, index) => ( {constraints.map((constraint, index) => {
// biome-ignore lint: reason=objectId would change every time values change - this is no different than using index // biome-ignore lint: reason=objectId would change every time values change - this is no different than using index
<Fragment key={index}> const id = constraint[constraintId];
return (
<Fragment key={id}>
<ConditionallyRender <ConditionallyRender
condition={index > 0} condition={index > 0}
show={<StrategySeparator text='AND' />} show={<StrategySeparator text='AND' />}
@ -181,7 +182,8 @@ export const NewConstraintAccordionList = forwardRef<
compact compact
/> />
</Fragment> </Fragment>
))} );
})}
</StyledContainer> </StyledContainer>
); );
}); });

View File

@ -28,6 +28,8 @@ import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequ
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm'; import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm';
import { NewStrategyVariants } from 'component/feature/StrategyTypes/NewStrategyVariants'; import { NewStrategyVariants } from 'component/feature/StrategyTypes/NewStrategyVariants';
import { constraintId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
import { v4 as uuidv4 } from 'uuid';
const useTitleTracking = () => { const useTitleTracking = () => {
const [previousTitle, setPreviousTitle] = useState<string>(''); const [previousTitle, setPreviousTitle] = useState<string>('');
@ -75,6 +77,14 @@ const useTitleTracking = () => {
}; };
}; };
const addIdSymbolToConstraints = (strategy?: IFeatureStrategy) => {
if (!strategy) return;
return strategy?.constraints.map((constraint) => {
return { ...constraint, [constraintId]: uuidv4() };
});
};
export const NewFeatureStrategyEdit = () => { export const NewFeatureStrategyEdit = () => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId'); const featureId = useRequiredPathParam('featureId');
@ -133,7 +143,15 @@ export const NewFeatureStrategyEdit = () => {
const savedStrategy = data?.environments const savedStrategy = data?.environments
.flatMap((environment) => environment.strategies) .flatMap((environment) => environment.strategies)
.find((strategy) => strategy.id === strategyId); .find((strategy) => strategy.id === strategyId);
setStrategy((prev) => ({ ...prev, ...savedStrategy }));
const constraintsWithId = addIdSymbolToConstraints(savedStrategy);
const formattedStrategy = {
...savedStrategy,
constraints: constraintsWithId,
};
setStrategy((prev) => ({ ...prev, ...formattedStrategy }));
setPreviousTitle(savedStrategy?.title || ''); setPreviousTitle(savedStrategy?.title || '');
}, [strategyId, data]); }, [strategyId, data]);

View File

@ -1,5 +1,6 @@
import { Operator } from 'constants/operators'; import { Operator } from 'constants/operators';
import { IFeatureVariant } from './featureToggle'; import { IFeatureVariant } from './featureToggle';
import { constraintId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
export interface IFeatureStrategy { export interface IFeatureStrategy {
id: string; id: string;
@ -61,6 +62,7 @@ export interface IConstraint {
caseInsensitive?: boolean; caseInsensitive?: boolean;
operator: Operator; operator: Operator;
contextName: string; contextName: string;
[constraintId]?: string;
} }
export interface IFeatureStrategySortOrder { export interface IFeatureStrategySortOrder {