Skip to content

Commit b1a1615

Browse files
talissoncostaclaude
andcommitted
fix(dependencies): attach the feature's current value to the change request
The API refuses a change request with no changes and cannot hold a dependency change alone until #8449, so the change request carries the feature's value unchanged. This also ties it to the feature, so its page shows it and can be published. Also truncates a long prerequisite name at its end only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent bf577c2 commit b1a1615

6 files changed

Lines changed: 79 additions & 4 deletions

File tree

‎frontend/common/services/useFeatureState.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,6 @@ export async function updateFeatureState(
109109
export const {
110110
useGetAllEnvironmentFeatureStatesQuery,
111111
useGetFeatureStatesQuery,
112+
useLazyGetFeatureStatesQuery,
112113
useUpdateFeatureStateMutation,
113114
} = featureStateService

‎frontend/common/types/requests.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
ChangeSet,
3+
MultivariateFeatureStateValue,
24
Account,
35
ExternalResource,
46
FeatureState,
@@ -893,6 +895,18 @@ export type Req = {
893895
description: string
894896
live_from?: string
895897
approvals: Approval[]
898+
change_sets?: (Omit<ChangeSet, 'feature_states_to_update'> & {
899+
// New states, so no ids, theirs or their variations'.
900+
feature_states_to_update: (Omit<
901+
FeatureState,
902+
'id' | 'multivariate_feature_state_values'
903+
> & {
904+
multivariate_feature_state_values?: Omit<
905+
MultivariateFeatureStateValue,
906+
'id'
907+
>[]
908+
})[]
909+
})[]
896910
}
897911
deleteChangeRequest: { id: number }
898912
getSamlConfiguration: { name: string }

‎frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.scss‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@
1515
background: var(--color-surface-muted);
1616
}
1717

18-
// A button only shrinks below its text with a min width.
18+
// A button only shrinks below its text with a min width, and centres its
19+
// text, which would clip a long name at both ends.
1920
.prerequisite-name > .btn {
2021
min-width: 0;
22+
justify-content: flex-start;
23+
}
24+
25+
.prerequisite-name__text {
26+
min-width: 0;
2127
}
2228

2329
// Held for a change request: still listed, but on its way out. On the inner

‎frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.tsx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,12 @@ const PrerequisitesTable: FC<PrerequisitesTableProps> = ({
127127
<div className='prerequisite-name d-flex align-items-center gap-2'>
128128
<Button
129129
theme='text'
130-
className='text-truncate'
131130
onClick={(e: MouseEvent) => {
132131
e.stopPropagation()
133132
onSelect(edge)
134133
}}
135134
>
136-
<span className='prerequisite-name__text'>
135+
<span className='prerequisite-name__text text-truncate'>
137136
{edge.prerequisite.name}
138137
</span>
139138
</Button>

‎frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/hooks/useStagedPrerequisites.tsx‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import {
1313
useCreateFeatureDependencyMutation,
1414
useDeleteFeatureDependencyMutation,
1515
} from 'common/services/useFeatureDependency'
16+
import { useLazyGetFeatureStatesQuery } from 'common/services/useFeatureState'
17+
import { useProjectEnvironments } from 'common/hooks/useProjectEnvironments'
1618
import ChangeRequestModal from 'components/modals/ChangeRequestModal'
19+
import { unchangedChangeSet } from 'components/modals/create-feature/tabs/FeatureDependenciesTab/unchangedChangeSet'
1720

1821
export type StagedChange = {
1922
action: 'add' | 'remove'
@@ -61,6 +64,8 @@ export const useStagedPrerequisites = ({
6164
const [deleteChangeRequest] = useDeleteChangeRequestMutation()
6265
const [createDependency] = useCreateFeatureDependencyMutation()
6366
const [deleteDependency] = useDeleteFeatureDependencyMutation()
67+
const [getFeatureStates] = useLazyGetFeatureStatesQuery()
68+
const { getEnvironmentIdFromKey } = useProjectEnvironments(projectId)
6469

6570
const drop = (prerequisiteId: number) =>
6671
setStaged((prev) =>
@@ -117,8 +122,21 @@ export const useStagedPrerequisites = ({
117122
setError(null)
118123
let changeRequestId: number | undefined
119124
try {
125+
const featureStates = await getFeatureStates({
126+
environment: getEnvironmentIdFromKey(environmentId),
127+
feature: projectFlag.id,
128+
}).unwrap()
129+
const changeSet = unchangedChangeSet(
130+
projectFlag.id,
131+
featureStates.results,
132+
fields.live_from,
133+
)
120134
changeRequestId = (
121-
await createChangeRequest({ environmentId, ...fields }).unwrap()
135+
await createChangeRequest({
136+
environmentId,
137+
...fields,
138+
change_sets: changeSet ? [changeSet] : undefined,
139+
}).unwrap()
122140
).id
123141
await stage(changeRequestId)
124142
setStaged([])
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { FeatureState } from 'common/types/responses'
2+
import { Req } from 'common/types/requests'
3+
4+
type ChangeSetBody = NonNullable<
5+
Req['createEnvironmentChangeRequest']['change_sets']
6+
>[number]
7+
8+
// The API refuses a change request with no changes, and cannot hold a
9+
// dependency change on its own until #8449. Until then, the change request
10+
// carries the feature's current value unchanged, which also ties it to the
11+
// feature so its page shows it. Publishing it makes an identical version.
12+
export const unchangedChangeSet = (
13+
featureId: number,
14+
featureStates: FeatureState[],
15+
liveFrom: string | undefined,
16+
): ChangeSetBody | undefined => {
17+
const current = featureStates.find(
18+
(state) => !state.feature_segment && !state.identity,
19+
)
20+
if (!current) return
21+
const { id: _id, multivariate_feature_state_values, ...state } = current
22+
return {
23+
feature: featureId,
24+
feature_states_to_create: [],
25+
feature_states_to_update: [
26+
{
27+
...state,
28+
multivariate_feature_state_values:
29+
multivariate_feature_state_values?.map(
30+
({ id: _valueId, ...value }) => value,
31+
),
32+
},
33+
],
34+
live_from: liveFrom ?? new Date().toISOString(),
35+
segment_ids_to_delete_overrides: [],
36+
}
37+
}

0 commit comments

Comments
 (0)