diff --git a/frontend/common/hooks/useFeatureStates.ts b/frontend/common/hooks/useFeatureStates.ts index e87e86681ff5..5d1a6a593775 100644 --- a/frontend/common/hooks/useFeatureStates.ts +++ b/frontend/common/hooks/useFeatureStates.ts @@ -9,6 +9,7 @@ type UseFeatureStatesArgs = { // The api key, as everywhere else in the UI. environmentId: string projectId: number + skip?: boolean } // Whether each of a project's features is on in one environment, from the @@ -16,6 +17,7 @@ type UseFeatureStatesArgs = { export const useFeatureStates = ({ environmentId, projectId, + skip, }: UseFeatureStatesArgs) => { const { error: environmentsError, @@ -35,7 +37,7 @@ export const useFeatureStates = ({ page_size: 999, projectId, }, - { skip: !numericEnvId }, + { skip: skip || !numericEnvId }, ) const features = data?.results ?? NO_FEATURES diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index 9c6096351ed8..3ef3f6f99efd 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -187,7 +187,6 @@ export type DependencyEdge = { export type StagedAction = 'add' | 'remove' -// A dependency held in a change request, not live until it is published. export type StagedDependencyChange = { action: StagedAction prerequisite: DependencyFeature @@ -247,7 +246,10 @@ export type Segment = { metadata: Metadata[] | [] membership_counts?: SegmentMembership[] cohort?: SegmentCohort | null + managed_by?: SegmentManagedBy } +// What creates and edits the segment, empty where users do. +export type SegmentManagedBy = '' | 'cohort' | 'dependency' | 'experiment' export type ProjectChangeRequest = Omit< ChangeRequest, | 'environment_feature_versions' diff --git a/frontend/common/utils/__tests__/diffDependencies.test.ts b/frontend/common/utils/__tests__/diffDependencies.test.ts new file mode 100644 index 000000000000..2202a11777f7 --- /dev/null +++ b/frontend/common/utils/__tests__/diffDependencies.test.ts @@ -0,0 +1,40 @@ +import { DependencyEdge } from 'common/types/responses' +import { diffDependencies } from 'common/utils/diffDependencies' + +const edge = (prerequisiteId: number): DependencyEdge => ({ + feature: { id: 1, name: 'checkout' }, + prerequisite: { id: prerequisiteId, name: `prereq_${prerequisiteId}` }, + segment: { + condition_json_path: '', + id: 1, + is_system: true, + name: 'checkout', + rules: [], + }, +}) + +describe('diffDependencies', () => { + it('reads a prerequisite only in the change request as an add', () => { + expect(diffDependencies([edge(1)], [edge(1), edge(2)])).toEqual([ + { action: 'add', prerequisite: { id: 2, name: 'prereq_2' } }, + ]) + }) + + it('reads a live prerequisite missing from the change request as a removal', () => { + expect(diffDependencies([edge(1), edge(2)], [edge(1)])).toEqual([ + { action: 'remove', prerequisite: { id: 2, name: 'prereq_2' } }, + ]) + }) + + it('lists adds before removals', () => { + expect( + diffDependencies([edge(1), edge(2)], [edge(1), edge(3)]).map( + (change) => change.action, + ), + ).toEqual(['add', 'remove']) + }) + + it('finds no change when the lists match', () => { + expect(diffDependencies([edge(1)], [edge(1)])).toEqual([]) + }) +}) diff --git a/frontend/common/utils/diffDependencies.ts b/frontend/common/utils/diffDependencies.ts new file mode 100644 index 000000000000..ea19e63feeac --- /dev/null +++ b/frontend/common/utils/diffDependencies.ts @@ -0,0 +1,29 @@ +import { DependencyEdge, StagedDependencyChange } from 'common/types/responses' + +// The API lists a change request's dependencies as if it were live, so what +// it changes is that list against today's. +export const diffDependencies = ( + live: DependencyEdge[], + scoped: DependencyEdge[], +): StagedDependencyChange[] => { + const liveIds = new Set(live.map((edge) => edge.prerequisite.id)) + const scopedIds = new Set(scoped.map((edge) => edge.prerequisite.id)) + return [ + ...scoped + .filter((edge) => !liveIds.has(edge.prerequisite.id)) + .map( + ({ prerequisite }): StagedDependencyChange => ({ + action: 'add', + prerequisite, + }), + ), + ...live + .filter((edge) => !scopedIds.has(edge.prerequisite.id)) + .map( + ({ prerequisite }): StagedDependencyChange => ({ + action: 'remove', + prerequisite, + }), + ), + ] +} diff --git a/frontend/documentation/components/Chip.stories.tsx b/frontend/documentation/components/Chip.stories.tsx index c88fe9860935..068a95042d1b 100644 --- a/frontend/documentation/components/Chip.stories.tsx +++ b/frontend/documentation/components/Chip.stories.tsx @@ -28,6 +28,10 @@ export const Accent: Story = { args: { children: '"hello"', variant: 'accent' }, } +export const Danger: Story = { + args: { children: 'Rejected', variant: 'danger' }, +} + export const Sizes: Story = { render: () => (
diff --git a/frontend/web/components/base/Chip/Chip.scss b/frontend/web/components/base/Chip/Chip.scss index 854427ce3f98..a55ba57d0212 100644 --- a/frontend/web/components/base/Chip/Chip.scss +++ b/frontend/web/components/base/Chip/Chip.scss @@ -13,6 +13,10 @@ border-color: var(--color-border-action); } + &--danger { + border-color: var(--color-border-danger); + } + // Sizes (default is the base above). &--sm { padding: 3px 8px; diff --git a/frontend/web/components/base/Chip/Chip.tsx b/frontend/web/components/base/Chip/Chip.tsx index 296219372dda..2aae4d2edc89 100644 --- a/frontend/web/components/base/Chip/Chip.tsx +++ b/frontend/web/components/base/Chip/Chip.tsx @@ -5,7 +5,7 @@ import { colorIconSecondary } from 'common/theme/tokens' import './Chip.scss' export type ChipSize = 'default' | 'sm' | 'xs' -export type ChipVariant = 'neutral' | 'accent' +export type ChipVariant = 'neutral' | 'accent' | 'danger' export type ChipProps = { children: ReactNode @@ -30,6 +30,7 @@ export type ChipProps = { // bg + text come from token utilities; the variant border lives in Chip.scss. const VARIANT_UTILITIES: Record = { accent: 'bg-surface-action-subtle text-action', + danger: 'bg-transparent text-danger', neutral: 'bg-surface-subtle text-default', } @@ -63,6 +64,7 @@ const Chip = ({ { 'ds-chip--accent': variant === 'accent', 'ds-chip--clickable': interactive, + 'ds-chip--danger': variant === 'danger', [`ds-chip--${size}`]: size !== 'default', 'ds-chip--truncate': truncate, }, diff --git a/frontend/web/components/diff/DiffChangeRequest.tsx b/frontend/web/components/diff/DiffChangeRequest.tsx index fbf1bf526e49..09b443c01113 100644 --- a/frontend/web/components/diff/DiffChangeRequest.tsx +++ b/frontend/web/components/diff/DiffChangeRequest.tsx @@ -3,6 +3,10 @@ import { ChangeRequest } from 'common/types/responses' import { useGetFeatureStatesQuery } from 'common/services/useFeatureState' import DiffFeature from './DiffFeature' import { mergeChangeSets } from 'common/services/useChangeRequest' +import { useFeatureStates } from 'common/hooks/useFeatureStates' +import { useDependencyChanges } from 'components/feature-dependencies/useDependencyChanges' +import { useHasPermission } from 'common/providers/Permission' +import { EnvironmentPermission } from 'common/types/permissions.types' type DiffChangeRequestType = { changeRequest: ChangeRequest | null @@ -38,6 +42,34 @@ const DiffChangeRequest: FC = ({ return mergeChangeSets(changeSets, data?.results, changeRequest?.conflicts) }, [changeRequest, feature, data]) + // The API lists a change request's dependencies only to those who can + // create change requests. + const { permission: canCreateChangeRequest } = useHasPermission({ + id: environmentId, + level: 'environment', + permission: EnvironmentPermission.CREATE_CHANGE_REQUEST, + }) + const skipDependencies = + !changeRequest || + // The API rejects these. + changeRequest.is_committed || + !isVersioned || + !canCreateChangeRequest || + !Utils.getFlagsmithHasFeature('flag_dependencies') || + !Utils.getFlagsmithHasFeature('flag_dependencies_change_requests') + const dependencies = useDependencyChanges({ + changeRequestId: changeRequest?.id ?? 0, + environmentId, + featureId: feature, + skip: skipDependencies, + }) + // Shows if an added prerequisite is off. + const states = useFeatureStates({ + environmentId, + projectId: Number(projectId), + skip: skipDependencies, + }) + if (!changeRequest) { return null } @@ -52,6 +84,12 @@ const DiffChangeRequest: FC = ({ return ( boolean + } } type ViewMode = 'combined' | 'new' | 'old' const DiffFeature: FC = ({ conflicts, + dependencies, disableSegments, environmentId, featureId, @@ -71,17 +80,36 @@ const DiffFeature: FC = ({ projectId, }) + // Dependency segments get their own tab once loaded. + const overrideSegments = dependencies?.changes + ? segments?.results.filter((segment) => segment.managed_by !== 'dependency') + : segments?.results const segmentDiffs = disableSegments ? { diffs: [], totalChanges: 0 } - : getSegmentOverrideDiff(oldState, newState, segments?.results, conflicts) + : getSegmentOverrideDiff(oldState, newState, overrideSegments, conflicts) const variationDiffs = getVariationDiff(oldEnv, newEnv) const totalSegmentChanges = segmentDiffs?.totalChanges const totalVariationChanges = variationDiffs?.totalChanges + const totalDependencyChanges = dependencies?.changes?.length ?? 0 + const showDependencies = !!totalDependencyChanges || !!dependencies?.isError + // Each tab's change count, in the order the tabs render. + const tabChanges = [ + totalChanges, + ...(variationDiffs?.diffs?.length ? [totalVariationChanges] : []), + ...(segmentDiffs?.diffs.length ? [totalSegmentChanges] : []), + ...(showDependencies ? [totalDependencyChanges] : []), + ] + const changedTab = tabChanges.findIndex((changes) => !!changes) + // With no other changes, open Dependencies (the last tab) to show its error. + const firstChangedTab = + changedTab === -1 && dependencies?.isError + ? tabChanges.length - 1 + : changedTab useEffect(() => { - if (!totalChanges && (totalSegmentChanges || totalVariationChanges)) { - setValue(1) + if (firstChangedTab > 0) { + setValue(firstChangedTab) } - }, [totalSegmentChanges, totalVariationChanges, totalChanges]) + }, [firstChangedTab]) const hideValue = !totalChanges && (diff.newValue === null || diff.newValue === undefined) const viewOptions = [ @@ -245,6 +273,31 @@ const DiffFeature: FC = ({ /> )} + {showDependencies && ( + + Dependencies + {!!totalDependencyChanges && ( + {totalDependencyChanges} + )} +
+ } + > + {dependencies?.isError ? ( + + Could not load the dependency changes in this change + request. + + ) : ( + + )} + + )} )} diff --git a/frontend/web/components/feature-dependencies/DependencyChangesTable/DependencyChangesTable.scss b/frontend/web/components/feature-dependencies/DependencyChangesTable/DependencyChangesTable.scss new file mode 100644 index 000000000000..7602c4c7140c --- /dev/null +++ b/frontend/web/components/feature-dependencies/DependencyChangesTable/DependencyChangesTable.scss @@ -0,0 +1,8 @@ +// Fixed widths so the names line up across tables. +.dependency-changes__change { + width: 48px; +} + +.dependency-changes__state { + width: 120px; +} diff --git a/frontend/web/components/feature-dependencies/DependencyChangesTable/DependencyChangesTable.tsx b/frontend/web/components/feature-dependencies/DependencyChangesTable/DependencyChangesTable.tsx new file mode 100644 index 000000000000..f34968b447db --- /dev/null +++ b/frontend/web/components/feature-dependencies/DependencyChangesTable/DependencyChangesTable.tsx @@ -0,0 +1,83 @@ +import { FC } from 'react' +import { StagedDependencyChange } from 'common/types/responses' +import cn from 'classnames' +import StateToggle from 'components/feature-dependencies/StateToggle' +import Satisfies from 'components/feature-dependencies/Satisfies' +import 'components/diff/DiffRow/DiffRow.scss' +import './DependencyChangesTable.scss' +import Table, { + TableBody, + TableCell, + TableColumnHeader, + TableHeader, + TableRow, +} from 'components/base/Table' + +type DependencyChangesTableProps = { + changes: StagedDependencyChange[] + // Adds the live table's Status and Satisfies columns for this environment. + isEnabled?: (prerequisiteId: number) => boolean +} + +const DependencyChangesTable: FC = ({ + changes, + isEnabled, +}) => ( + + + + + Prerequisite + {!!isEnabled && ( + <> + + Status + + + Satisfies + + + )} + + + + {changes.map(({ action, prerequisite }) => { + const isAdd = action === 'add' + return ( + + +
+                {isAdd ? '+' : '-'}
+              
+
+ + {prerequisite.name} + + {!!isEnabled && ( + <> + + + + + {/* Only system edges can be staged, so on means met. */} + + + + )} +
+ ) + })} +
+
+) + +export default DependencyChangesTable diff --git a/frontend/web/components/feature-dependencies/DependencyChangesTable/index.ts b/frontend/web/components/feature-dependencies/DependencyChangesTable/index.ts new file mode 100644 index 000000000000..4564a17ff442 --- /dev/null +++ b/frontend/web/components/feature-dependencies/DependencyChangesTable/index.ts @@ -0,0 +1 @@ +export { default } from './DependencyChangesTable' diff --git a/frontend/web/components/feature-dependencies/useDependencyChanges.ts b/frontend/web/components/feature-dependencies/useDependencyChanges.ts new file mode 100644 index 000000000000..3682df154dfb --- /dev/null +++ b/frontend/web/components/feature-dependencies/useDependencyChanges.ts @@ -0,0 +1,30 @@ +import { skipToken } from '@reduxjs/toolkit/query' +import { useGetFeatureDependenciesQuery } from 'common/services/useFeatureDependency' +import { diffDependencies } from 'common/utils/diffDependencies' + +type UseDependencyChangesArgs = { + environmentId: string + featureId: number + changeRequestId: number + skip?: boolean +} + +export const useDependencyChanges = ({ + changeRequestId, + environmentId, + featureId, + skip, +}: UseDependencyChangesArgs) => { + const live = useGetFeatureDependenciesQuery( + skip ? skipToken : { environmentId, featureId }, + ) + const scoped = useGetFeatureDependenciesQuery( + skip ? skipToken : { changeRequestId, environmentId, featureId }, + ) + const isError = live.isError || scoped.isError + const changes = + live.data && scoped.data + ? diffDependencies(live.data.results, scoped.data.results) + : undefined + return { changes, isError } +} diff --git a/frontend/web/components/modals/create-feature/hoc/FeatureProvider.tsx b/frontend/web/components/modals/create-feature/hoc/FeatureProvider.tsx index 254fec52a67f..790384afbcac 100644 --- a/frontend/web/components/modals/create-feature/hoc/FeatureProvider.tsx +++ b/frontend/web/components/modals/create-feature/hoc/FeatureProvider.tsx @@ -23,13 +23,7 @@ export default function withFeatureProvider( this.listenTo( FeatureListStore, 'saved', - ({ - changeRequest, - createdFlag, - error, - isCreate, - updatedChangeRequest, - }: any = {}) => { + ({ changeRequest, createdFlag, error, isCreate }: any = {}) => { if (error?.data?.metadata) { error.data.metadata?.forEach((m: any) => { if (Object.keys(m).length > 0) { @@ -39,28 +33,10 @@ export default function withFeatureProvider( } else if (error?.data) { toast('Error updating the Flag', 'danger') return - } else { - const isEditingChangeRequest = - this.props.changeRequest && changeRequest + } else if (!changeRequest) { + // The modal toasts a change request once its dependencies are staged. const operation = createdFlag || isCreate ? 'Created' : 'Updated' - const type = changeRequest ? 'Change Request' : 'Feature' - - const toastText = isEditingChangeRequest - ? `Updated ${type}` - : `${operation} ${type}` - const toastAction = changeRequest - ? { - buttonText: 'Open', - onClick: () => { - closeModal() - this.props.history.push( - `/project/${this.props.projectId}/environment/${this.props.environmentId}/change-requests/${updatedChangeRequest?.id}`, - ) - }, - } - : undefined - - toast(toastText, 'success', undefined, toastAction) + toast(`${operation} Feature`, 'success') } const envFlags = FeatureListStore.getEnvironmentFlags() diff --git a/frontend/web/components/modals/create-feature/index.tsx b/frontend/web/components/modals/create-feature/index.tsx index 18a35671864b..e7ab3f699719 100644 --- a/frontend/web/components/modals/create-feature/index.tsx +++ b/frontend/web/components/modals/create-feature/index.tsx @@ -156,8 +156,7 @@ const CreateFeatureModal: FC = (props) => { const [, setTabKey] = useState(0) const isEdit = !!props.projectFlag - // Held here rather than in the tab, so the change request any tab creates - // takes them along. + // In the modal, so a change request from any tab includes them. const dependencyChanges = useStagedPrerequisites({ environmentId, projectFlag, @@ -497,6 +496,22 @@ const CreateFeatureModal: FC = (props) => { } const Provider = identity ? IdentityProvider : FeatureListProvider + + const toastChangeRequestSaved = (changeRequestId: number) => + toast( + `${existingChangeRequest ? 'Updated' : 'Created'} Change Request`, + 'success', + undefined, + { + buttonText: 'Open', + onClick: () => { + closeModal() + props.history?.push( + `/project/${projectId}/environment/${environmentId}/change-requests/${changeRequestId}`, + ) + }, + }, + ) const environmentName = environment.name return ( @@ -508,10 +523,22 @@ const CreateFeatureModal: FC = (props) => { setValueChanged(true) } }} - onSave={(data?: { updatedChangeRequest?: { id: number } }) => { - const changeRequestId = data?.updatedChangeRequest?.id - if (changeRequestId && dependenciesChanged) { - dependencyChanges.stageInto(changeRequestId) + onSave={(data?: { + changeRequest?: boolean + updatedChangeRequest?: { id: number } + }) => { + const changeRequestId = data?.changeRequest + ? data.updatedChangeRequest?.id + : undefined + if (changeRequestId) { + const staging = dependenciesChanged + ? dependencyChanges.stageInto(changeRequestId) + : Promise.resolve(null) + // A change request missing some dependency changes gets the banner, + // not a success toast. + staging.then((failure) => { + if (!failure) toastChangeRequestSaved(changeRequestId) + }) } if (identity) { close() diff --git a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/FeatureDependenciesTab.tsx b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/FeatureDependenciesTab.tsx index 27927f57fb60..ad4e39a60d95 100644 --- a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/FeatureDependenciesTab.tsx +++ b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/FeatureDependenciesTab.tsx @@ -28,7 +28,6 @@ type FeatureDependenciesTabProps = { projectFlag: ProjectFlag requiresChangeRequests: boolean isVersioned: boolean - // Held by the modal, so any tab's change request takes them along. staging: StagedPrerequisites // The modal is creating the change request the held changes go into. isSaving?: boolean @@ -95,7 +94,6 @@ const FeatureDependenciesTab: FC = ({ return } - // Where change requests are required, edits are held instead of written. const editing = isChangeRequest ? { onAdd: (feature: ProjectFlag) => { diff --git a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.scss b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.scss index 54d52a48d425..56b6491c76fc 100644 --- a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.scss +++ b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.scss @@ -15,11 +15,6 @@ background: var(--color-surface-muted); } -// On the cell, as with the input row, so hover does not replace it. -.prerequisite-row--refused td { - background: var(--color-surface-danger); -} - // The select needs permission to shrink or it pushes the row past the panel. .prerequisite-add-control > :first-child { flex: 1 1 auto; diff --git a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.tsx b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.tsx index 08475d434dd8..f2d5881f0755 100644 --- a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.tsx +++ b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/PrerequisitesTable.tsx @@ -2,7 +2,6 @@ import { FC, MouseEvent, ReactNode } from 'react' import cn from 'classnames' import { DependencyEdge } from 'common/types/responses' import Button from 'components/base/forms/Button' -import Chip from 'components/base/Chip' import Table, { TableBody, TableCell, @@ -15,6 +14,7 @@ import Icon from 'components/icons/Icon' import { PrerequisiteRow } from 'components/modals/create-feature/tabs/FeatureDependenciesTab/prerequisiteState' import StateToggle from 'components/feature-dependencies/StateToggle' import Satisfies from 'components/feature-dependencies/Satisfies' +import StagedChip from './StagedChip' import './PrerequisitesTable.scss' // The actions column only exists where the user can manage dependencies, so the @@ -27,9 +27,8 @@ type PrerequisitesTableProps = { flashedId?: number isRemoving?: number onRemove: (edge: DependencyEdge) => void - // Takes back a held removal. onUndo?: (edge: DependencyEdge) => void - // The row a change request refused, marked until the user acts. + // The held change the API rejected. refusedId?: number onSelect: (edge: DependencyEdge) => void // The prerequisite being added, shown as a row of its own until the POST @@ -72,7 +71,6 @@ const PrerequisitesTable: FC = ({ key={edge.prerequisite.id} className={cn('cursor-pointer', { 'prerequisite-row--flash': flashedId === edge.prerequisite.id, - 'prerequisite-row--refused': refusedId === edge.prerequisite.id, })} onClick={() => onSelect(edge)} pending={isRemoving === edge.prerequisite.id} @@ -99,13 +97,10 @@ const PrerequisitesTable: FC = ({ {!!staged && ( - - {staged === 'add' ? 'Staged: add' : 'Staged: remove'} - + )} diff --git a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/StagedChip.tsx b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/StagedChip.tsx new file mode 100644 index 000000000000..adb0f4ec7152 --- /dev/null +++ b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/PrerequisitesTable/StagedChip.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react' +import { StagedAction } from 'common/types/responses' +import Chip, { ChipVariant } from 'components/base/Chip' + +type StagedChipProps = { + action: StagedAction + isRefused?: boolean +} + +const variant = (action: StagedAction, isRefused?: boolean): ChipVariant => { + if (isRefused) return 'danger' + return action === 'add' ? 'accent' : 'neutral' +} + +const StagedChip: FC = ({ action, isRefused }) => ( + + {isRefused ? 'Rejected' : 'Staged'}: {action} + +) + +export default StagedChip diff --git a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/dependenciesMode.ts b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/dependenciesMode.ts index 822e49634d5a..0531d21e2530 100644 --- a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/dependenciesMode.ts +++ b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/dependenciesMode.ts @@ -1,6 +1,6 @@ // How dependency changes reach an environment: written directly, held for a // change request, or not at all where change requests run on v1 versioning, -// which #8449 does not support. +// which isn't supported. export type DependenciesMode = 'direct' | 'changeRequest' | 'readOnly' export const getDependenciesMode = ( diff --git a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/hooks/useStagedPrerequisites.tsx b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/hooks/useStagedPrerequisites.tsx index 65aba2ec38fb..a1e2fdd7b942 100644 --- a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/hooks/useStagedPrerequisites.tsx +++ b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/hooks/useStagedPrerequisites.tsx @@ -9,7 +9,6 @@ import { useDeleteFeatureDependencyMutation, } from 'common/services/useFeatureDependency' -// Why a change could not be staged, and the row it belongs to. export type StagingFailure = { message: string; prerequisiteId?: number } // Dependency refusals answer { message }, other errors { detail }. @@ -87,9 +86,7 @@ export const useStagedPrerequisites = ({ return null } - // Into the change request the modal created with the value and segment - // overrides. Those that made it in leave the held list, so the rest can be - // tried again. + // Staged changes leave the list, so a retry sends only the rest. const stageInto = async (changeRequestId: number) => { setIsSubmitting(true) setError(null) @@ -107,6 +104,7 @@ export const useStagedPrerequisites = ({ setStaged([]) } setIsSubmitting(false) + return failure } return { @@ -120,7 +118,7 @@ export const useStagedPrerequisites = ({ remove, stageInto, staged, - // The refusal was about the held changes, so it goes with them. + // Undoing clears the error too. undo: (prerequisiteId: number) => { setError(null) drop(prerequisiteId) diff --git a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/prerequisiteState.ts b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/prerequisiteState.ts index c81fe9ea672f..4545aaf77d67 100644 --- a/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/prerequisiteState.ts +++ b/frontend/web/components/modals/create-feature/tabs/FeatureDependenciesTab/prerequisiteState.ts @@ -11,7 +11,6 @@ export type PrerequisiteRow = { isEnabled: boolean // Undefined where the rule cannot be read. See toPrerequisiteRow. isMet?: boolean - // Held for a change request, not live yet. staged?: StagedAction }