mirror of
https://github.com/Unleash/unleash.git
synced 2025-11-10 01:19:53 +01:00
Enforce [constraintId] on incoming constraints
This commit is contained in:
parent
71c9a53380
commit
872c582eb9
@ -9,6 +9,7 @@ import {
|
|||||||
getSegmentChangesThatWouldBeOverwritten,
|
getSegmentChangesThatWouldBeOverwritten,
|
||||||
getStrategyChangesThatWouldBeOverwritten,
|
getStrategyChangesThatWouldBeOverwritten,
|
||||||
} from './strategy-change-diff-calculation.js';
|
} from './strategy-change-diff-calculation.js';
|
||||||
|
import { constraintId } from 'constants/constraintId.js';
|
||||||
|
|
||||||
describe('Strategy change conflict detection', () => {
|
describe('Strategy change conflict detection', () => {
|
||||||
const existingStrategy: IFeatureStrategy = {
|
const existingStrategy: IFeatureStrategy = {
|
||||||
@ -175,6 +176,7 @@ describe('Strategy change conflict detection', () => {
|
|||||||
operator: 'IN' as const,
|
operator: 'IN' as const,
|
||||||
contextName: 'appName',
|
contextName: 'appName',
|
||||||
caseInsensitive: false,
|
caseInsensitive: false,
|
||||||
|
[constraintId]: 'id1',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
variants: [
|
variants: [
|
||||||
@ -230,6 +232,7 @@ describe('Strategy change conflict detection', () => {
|
|||||||
operator: 'IN' as const,
|
operator: 'IN' as const,
|
||||||
contextName: 'appName',
|
contextName: 'appName',
|
||||||
caseInsensitive: false,
|
caseInsensitive: false,
|
||||||
|
[constraintId]: 'id2',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -249,6 +252,7 @@ describe('Strategy change conflict detection', () => {
|
|||||||
inverted: false,
|
inverted: false,
|
||||||
operator: 'IN' as const,
|
operator: 'IN' as const,
|
||||||
values: ['blah'],
|
values: ['blah'],
|
||||||
|
[constraintId]: 'id2',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -478,6 +482,7 @@ describe('Segment change conflict detection', () => {
|
|||||||
operator: 'IN' as const,
|
operator: 'IN' as const,
|
||||||
contextName: 'appName',
|
contextName: 'appName',
|
||||||
caseInsensitive: false,
|
caseInsensitive: false,
|
||||||
|
[constraintId]: 'id3',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -494,6 +499,7 @@ describe('Segment change conflict detection', () => {
|
|||||||
operator: 'IN' as const,
|
operator: 'IN' as const,
|
||||||
contextName: 'appName',
|
contextName: 'appName',
|
||||||
caseInsensitive: false,
|
caseInsensitive: false,
|
||||||
|
[constraintId]: 'id4',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import {
|
|||||||
} from '@server/types/permissions';
|
} from '@server/types/permissions';
|
||||||
import { StrategyItem } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem';
|
import { StrategyItem } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem';
|
||||||
import type { IFeatureStrategy } from 'interfaces/strategy';
|
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
import { constraintId } from 'constants/constraintId';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
interface ProjectEnvironmentDefaultStrategyProps {
|
interface ProjectEnvironmentDefaultStrategyProps {
|
||||||
environment: ProjectEnvironmentType;
|
environment: ProjectEnvironmentType;
|
||||||
@ -55,7 +57,11 @@ export const ProjectEnvironmentDefaultStrategy = ({
|
|||||||
return {
|
return {
|
||||||
...baseDefaultStrategy,
|
...baseDefaultStrategy,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
constraints: baseDefaultStrategy.constraints ?? [],
|
constraints:
|
||||||
|
baseDefaultStrategy.constraints?.map((constraint) => ({
|
||||||
|
...constraint,
|
||||||
|
[constraintId]: uuidv4(),
|
||||||
|
})) ?? [],
|
||||||
title: baseDefaultStrategy.title ?? '',
|
title: baseDefaultStrategy.title ?? '',
|
||||||
parameters: baseDefaultStrategy.parameters ?? {},
|
parameters: baseDefaultStrategy.parameters ?? {},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,22 @@
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
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,
|
||||||
|
IChangeRequestAddStrategy,
|
||||||
|
IChangeRequestUpdateStrategy,
|
||||||
|
IFeatureChange,
|
||||||
|
} from 'component/changeRequest/changeRequest.types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { constraintId } from 'constants/constraintId.js';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
const isAddStrategyChange = (
|
||||||
|
change: IFeatureChange,
|
||||||
|
): change is IChangeRequestAddStrategy => change.action === 'addStrategy';
|
||||||
|
const isUpdateStrategyChange = (
|
||||||
|
change: IFeatureChange,
|
||||||
|
): change is IChangeRequestUpdateStrategy => change.action === 'updateStrategy';
|
||||||
|
|
||||||
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>(
|
||||||
@ -10,8 +25,47 @@ export const useChangeRequest = (projectId: string, id: string) => {
|
|||||||
{ refreshInterval: 15000 },
|
{ refreshInterval: 15000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dataWithConstraintIds: ChangeRequestType | undefined = useMemo(() => {
|
||||||
|
if (!data) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const features = data.features.map((feature) => {
|
||||||
|
const changes: IFeatureChange[] = feature.changes.map((change) => {
|
||||||
|
if (
|
||||||
|
isAddStrategyChange(change) ||
|
||||||
|
isUpdateStrategyChange(change)
|
||||||
|
) {
|
||||||
|
const { constraints, ...rest } = change.payload;
|
||||||
|
return {
|
||||||
|
...change,
|
||||||
|
payload: {
|
||||||
|
...rest,
|
||||||
|
constraints: constraints.map((constraint) => ({
|
||||||
|
...constraint,
|
||||||
|
[constraintId]: uuidv4(),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
} as IFeatureChange;
|
||||||
|
}
|
||||||
|
return change;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...feature,
|
||||||
|
changes,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const value: ChangeRequestType = {
|
||||||
|
...data,
|
||||||
|
features,
|
||||||
|
};
|
||||||
|
return value;
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data: dataWithConstraintIds,
|
||||||
loading: !error && !data,
|
loading: !error && !data,
|
||||||
refetchChangeRequest: () => mutate(),
|
refetchChangeRequest: () => mutate(),
|
||||||
error,
|
error,
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import useSWR, { type SWRConfiguration } from 'swr';
|
import useSWR, { type SWRConfiguration } from 'swr';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { emptyFeature } from './emptyFeature.ts';
|
import { emptyFeature } from './emptyFeature.ts';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler.ts';
|
import handleErrorResponses from '../httpErrorResponseHandler.ts';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
import type { IFeatureToggle } from 'interfaces/featureToggle';
|
import type { IFeatureToggle } from 'interfaces/featureToggle';
|
||||||
|
import { constraintId } from 'constants/constraintId.ts';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import type { IFeatureStrategy } from 'interfaces/strategy.ts';
|
||||||
|
|
||||||
export interface IUseFeatureOutput {
|
export interface IUseFeatureOutput {
|
||||||
feature: IFeatureToggle;
|
feature: IFeatureToggle;
|
||||||
@ -35,8 +38,10 @@ export const useFeature = (
|
|||||||
mutate().catch(console.warn);
|
mutate().catch(console.warn);
|
||||||
}, [mutate]);
|
}, [mutate]);
|
||||||
|
|
||||||
|
const feature = useMemo(enrichConstraintsWithIds(data), [data?.body]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
feature: data?.body || emptyFeature,
|
feature,
|
||||||
refetchFeature,
|
refetchFeature,
|
||||||
loading: !error && !data,
|
loading: !error && !data,
|
||||||
status: data?.status,
|
status: data?.status,
|
||||||
@ -63,6 +68,41 @@ export const featureFetcher = async (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const enrichConstraintsWithIds = (data?: IFeatureResponse) => () => {
|
||||||
|
if (!data?.body) {
|
||||||
|
return emptyFeature;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { strategies, environments, ...rest } = data.body;
|
||||||
|
|
||||||
|
const addConstraintIds = (strategy: IFeatureStrategy) => {
|
||||||
|
const { constraints, ...strategyRest } = strategy;
|
||||||
|
return {
|
||||||
|
...strategyRest,
|
||||||
|
constraints: constraints?.map((constraint) => ({
|
||||||
|
...constraint,
|
||||||
|
[constraintId]: uuidv4(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const strategiesWithConstraintIds = strategies?.map(addConstraintIds);
|
||||||
|
|
||||||
|
const environmentsWithStrategyIds = environments?.map((environment) => {
|
||||||
|
const { strategies, ...environmentRest } = environment;
|
||||||
|
return {
|
||||||
|
...environmentRest,
|
||||||
|
strategies: strategies?.map(addConstraintIds),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
strategies: strategiesWithConstraintIds,
|
||||||
|
environments: environmentsWithStrategyIds,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const formatFeatureApiPath = (
|
export const formatFeatureApiPath = (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
featureId: string,
|
featureId: string,
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import useSWRImmutable from 'swr/immutable';
|
import useSWRImmutable from 'swr/immutable';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { emptyFeature } from './emptyFeature.js';
|
|
||||||
import {
|
import {
|
||||||
type IUseFeatureOutput,
|
type IUseFeatureOutput,
|
||||||
type IFeatureResponse,
|
type IFeatureResponse,
|
||||||
featureFetcher,
|
featureFetcher,
|
||||||
formatFeatureApiPath,
|
formatFeatureApiPath,
|
||||||
useFeature,
|
useFeature,
|
||||||
|
enrichConstraintsWithIds,
|
||||||
} from 'hooks/api/getters/useFeature/useFeature';
|
} from 'hooks/api/getters/useFeature/useFeature';
|
||||||
|
|
||||||
// useFeatureImmutable is like useFeature, except it won't refetch data on
|
// useFeatureImmutable is like useFeature, except it won't refetch data on
|
||||||
@ -29,8 +29,10 @@ export const useFeatureImmutable = (
|
|||||||
await refetchFeature();
|
await refetchFeature();
|
||||||
}, [mutate, refetchFeature]);
|
}, [mutate, refetchFeature]);
|
||||||
|
|
||||||
|
const feature = useMemo(enrichConstraintsWithIds(data), [data?.body]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
feature: data?.body || emptyFeature,
|
feature,
|
||||||
refetchFeature: refetch,
|
refetchFeature: refetch,
|
||||||
loading: !error && !data,
|
loading: !error && !data,
|
||||||
status: data?.status,
|
status: data?.status,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user