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:
parent
af4c3a86d1
commit
967ee13e62
@ -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) => {
|
||||||
|
@ -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;
|
||||||
|
@ -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(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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,26 +160,30 @@ 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];
|
||||||
<ConditionallyRender
|
|
||||||
condition={index > 0}
|
|
||||||
show={<StrategySeparator text='AND' />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<NewConstraintAccordion
|
return (
|
||||||
constraint={constraint}
|
<Fragment key={id}>
|
||||||
onEdit={onEdit?.bind(null, constraint)}
|
<ConditionallyRender
|
||||||
onCancel={onCancel.bind(null, index)}
|
condition={index > 0}
|
||||||
onDelete={onRemove?.bind(null, index)}
|
show={<StrategySeparator text='AND' />}
|
||||||
onSave={onSave?.bind(null, index)}
|
/>
|
||||||
onAutoSave={onAutoSave?.bind(null, index)}
|
|
||||||
editing={Boolean(state.get(constraint)?.editing)}
|
<NewConstraintAccordion
|
||||||
compact
|
constraint={constraint}
|
||||||
/>
|
onEdit={onEdit?.bind(null, constraint)}
|
||||||
</Fragment>
|
onCancel={onCancel.bind(null, index)}
|
||||||
))}
|
onDelete={onRemove?.bind(null, index)}
|
||||||
|
onSave={onSave?.bind(null, index)}
|
||||||
|
onAutoSave={onAutoSave?.bind(null, index)}
|
||||||
|
editing={Boolean(state.get(constraint)?.editing)}
|
||||||
|
compact
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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]);
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user