Skip to content

Commit 872c582

Browse files
Enforce [constraintId] on incoming constraints
1 parent 71c9a53 commit 872c582

File tree

5 files changed

+116
-8
lines changed

5 files changed

+116
-8
lines changed

frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ChangeOverwriteWarning/strategy-change-diff-calculation.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getSegmentChangesThatWouldBeOverwritten,
1010
getStrategyChangesThatWouldBeOverwritten,
1111
} from './strategy-change-diff-calculation.js';
12+
import { constraintId } from 'constants/constraintId.js';
1213

1314
describe('Strategy change conflict detection', () => {
1415
const existingStrategy: IFeatureStrategy = {
@@ -175,6 +176,7 @@ describe('Strategy change conflict detection', () => {
175176
operator: 'IN' as const,
176177
contextName: 'appName',
177178
caseInsensitive: false,
179+
[constraintId]: 'id1',
178180
},
179181
],
180182
variants: [
@@ -230,6 +232,7 @@ describe('Strategy change conflict detection', () => {
230232
operator: 'IN' as const,
231233
contextName: 'appName',
232234
caseInsensitive: false,
235+
[constraintId]: 'id2',
233236
},
234237
],
235238
};
@@ -249,6 +252,7 @@ describe('Strategy change conflict detection', () => {
249252
inverted: false,
250253
operator: 'IN' as const,
251254
values: ['blah'],
255+
[constraintId]: 'id2',
252256
},
253257
],
254258
},
@@ -478,6 +482,7 @@ describe('Segment change conflict detection', () => {
478482
operator: 'IN' as const,
479483
contextName: 'appName',
480484
caseInsensitive: false,
485+
[constraintId]: 'id3',
481486
},
482487
],
483488
};
@@ -494,6 +499,7 @@ describe('Segment change conflict detection', () => {
494499
operator: 'IN' as const,
495500
contextName: 'appName',
496501
caseInsensitive: false,
502+
[constraintId]: 'id4',
497503
},
498504
],
499505
},

frontend/src/component/project/Project/ProjectSettings/ProjectDefaultStrategySettings/ProjectEnvironment/ProjectEnvironmentDefaultStrategy/ProjectEnvironmentDefaultStrategy.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
} from '@server/types/permissions';
1212
import { StrategyItem } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem';
1313
import type { IFeatureStrategy } from 'interfaces/strategy';
14+
import { constraintId } from 'constants/constraintId';
15+
import { v4 as uuidv4 } from 'uuid';
1416

1517
interface ProjectEnvironmentDefaultStrategyProps {
1618
environment: ProjectEnvironmentType;
@@ -55,7 +57,11 @@ export const ProjectEnvironmentDefaultStrategy = ({
5557
return {
5658
...baseDefaultStrategy,
5759
disabled: false,
58-
constraints: baseDefaultStrategy.constraints ?? [],
60+
constraints:
61+
baseDefaultStrategy.constraints?.map((constraint) => ({
62+
...constraint,
63+
[constraintId]: uuidv4(),
64+
})) ?? [],
5965
title: baseDefaultStrategy.title ?? '',
6066
parameters: baseDefaultStrategy.parameters ?? {},
6167
};

frontend/src/hooks/api/getters/useChangeRequest/useChangeRequest.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
import useSWR from 'swr';
22
import { formatApiPath } from 'utils/formatPath';
33
import handleErrorResponses from '../httpErrorResponseHandler.js';
4-
import type { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
4+
import type {
5+
ChangeRequestType,
6+
IChangeRequestAddStrategy,
7+
IChangeRequestUpdateStrategy,
8+
IFeatureChange,
9+
} from 'component/changeRequest/changeRequest.types';
10+
import { useMemo } from 'react';
11+
import { constraintId } from 'constants/constraintId.js';
12+
import { v4 as uuidv4 } from 'uuid';
13+
14+
const isAddStrategyChange = (
15+
change: IFeatureChange,
16+
): change is IChangeRequestAddStrategy => change.action === 'addStrategy';
17+
const isUpdateStrategyChange = (
18+
change: IFeatureChange,
19+
): change is IChangeRequestUpdateStrategy => change.action === 'updateStrategy';
520

621
export const useChangeRequest = (projectId: string, id: string) => {
722
const { data, error, mutate } = useSWR<ChangeRequestType>(
@@ -10,8 +25,47 @@ export const useChangeRequest = (projectId: string, id: string) => {
1025
{ refreshInterval: 15000 },
1126
);
1227

28+
const dataWithConstraintIds: ChangeRequestType | undefined = useMemo(() => {
29+
if (!data) {
30+
return data;
31+
}
32+
33+
const features = data.features.map((feature) => {
34+
const changes: IFeatureChange[] = feature.changes.map((change) => {
35+
if (
36+
isAddStrategyChange(change) ||
37+
isUpdateStrategyChange(change)
38+
) {
39+
const { constraints, ...rest } = change.payload;
40+
return {
41+
...change,
42+
payload: {
43+
...rest,
44+
constraints: constraints.map((constraint) => ({
45+
...constraint,
46+
[constraintId]: uuidv4(),
47+
})),
48+
},
49+
} as IFeatureChange;
50+
}
51+
return change;
52+
});
53+
54+
return {
55+
...feature,
56+
changes,
57+
};
58+
});
59+
60+
const value: ChangeRequestType = {
61+
...data,
62+
features,
63+
};
64+
return value;
65+
}, [data]);
66+
1367
return {
14-
data,
68+
data: dataWithConstraintIds,
1569
loading: !error && !data,
1670
refetchChangeRequest: () => mutate(),
1771
error,

frontend/src/hooks/api/getters/useFeature/useFeature.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import useSWR, { type SWRConfiguration } from 'swr';
2-
import { useCallback } from 'react';
2+
import { useCallback, useMemo } from 'react';
33
import { emptyFeature } from './emptyFeature.ts';
44
import handleErrorResponses from '../httpErrorResponseHandler.ts';
55
import { formatApiPath } from 'utils/formatPath';
66
import type { IFeatureToggle } from 'interfaces/featureToggle';
7+
import { constraintId } from 'constants/constraintId.ts';
8+
import { v4 as uuidv4 } from 'uuid';
9+
import type { IFeatureStrategy } from 'interfaces/strategy.ts';
710

811
export interface IUseFeatureOutput {
912
feature: IFeatureToggle;
@@ -35,8 +38,10 @@ export const useFeature = (
3538
mutate().catch(console.warn);
3639
}, [mutate]);
3740

41+
const feature = useMemo(enrichConstraintsWithIds(data), [data?.body]);
42+
3843
return {
39-
feature: data?.body || emptyFeature,
44+
feature,
4045
refetchFeature,
4146
loading: !error && !data,
4247
status: data?.status,
@@ -63,6 +68,41 @@ export const featureFetcher = async (
6368
};
6469
};
6570

71+
export const enrichConstraintsWithIds = (data?: IFeatureResponse) => () => {
72+
if (!data?.body) {
73+
return emptyFeature;
74+
}
75+
76+
const { strategies, environments, ...rest } = data.body;
77+
78+
const addConstraintIds = (strategy: IFeatureStrategy) => {
79+
const { constraints, ...strategyRest } = strategy;
80+
return {
81+
...strategyRest,
82+
constraints: constraints?.map((constraint) => ({
83+
...constraint,
84+
[constraintId]: uuidv4(),
85+
})),
86+
};
87+
};
88+
89+
const strategiesWithConstraintIds = strategies?.map(addConstraintIds);
90+
91+
const environmentsWithStrategyIds = environments?.map((environment) => {
92+
const { strategies, ...environmentRest } = environment;
93+
return {
94+
...environmentRest,
95+
strategies: strategies?.map(addConstraintIds),
96+
};
97+
});
98+
99+
return {
100+
...rest,
101+
strategies: strategiesWithConstraintIds,
102+
environments: environmentsWithStrategyIds,
103+
};
104+
};
105+
66106
export const formatFeatureApiPath = (
67107
projectId: string,
68108
featureId: string,

frontend/src/hooks/api/getters/useFeature/useFeatureImmutable.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import useSWRImmutable from 'swr/immutable';
2-
import { useCallback } from 'react';
3-
import { emptyFeature } from './emptyFeature.js';
2+
import { useCallback, useMemo } from 'react';
43
import {
54
type IUseFeatureOutput,
65
type IFeatureResponse,
76
featureFetcher,
87
formatFeatureApiPath,
98
useFeature,
9+
enrichConstraintsWithIds,
1010
} from 'hooks/api/getters/useFeature/useFeature';
1111

1212
// useFeatureImmutable is like useFeature, except it won't refetch data on
@@ -29,8 +29,10 @@ export const useFeatureImmutable = (
2929
await refetchFeature();
3030
}, [mutate, refetchFeature]);
3131

32+
const feature = useMemo(enrichConstraintsWithIds(data), [data?.body]);
33+
3234
return {
33-
feature: data?.body || emptyFeature,
35+
feature,
3436
refetchFeature: refetch,
3537
loading: !error && !data,
3638
status: data?.status,

0 commit comments

Comments
 (0)