diff --git a/frontend/common/providers/withSegmentOverrides.js b/frontend/common/providers/withSegmentOverrides.js index a958e8a51e70..b3fbbbe95e62 100644 --- a/frontend/common/providers/withSegmentOverrides.js +++ b/frontend/common/providers/withSegmentOverrides.js @@ -1,6 +1,7 @@ import data from 'common/data/base/_data' import ProjectStore from 'common/stores/project-store' import FeatureListStore from 'common/stores/feature-list-store' +import { mergeChangeSets } from 'common/services/useChangeRequest' export default (WrappedComponent) => { class HOC extends React.Component { @@ -28,6 +29,7 @@ export default (WrappedComponent) => { getOverrides = () => { if (this.props.projectFlag) { //todo: migrate to useSegmentFeatureState + const projectId = this.props.projectFlag.project Promise.all([ data.get( `${ @@ -43,7 +45,12 @@ export default (WrappedComponent) => { this.props.environmentId, )}&feature=${this.props.projectFlag.id}`, ), - ]).then(([res, res2]) => { + this.props.changeRequest?.change_sets + ? data.get( + `${Project.api}projects/${projectId}/segments/?page_size=1000`, + ) + : Promise.resolve({ results: [] }), + ]).then(([res, res2, segmentsRes]) => { const results = res.results const featureStates = res2.results const environmentOverride = res2.results.find( @@ -72,18 +79,115 @@ export default (WrappedComponent) => { } } }) - const resResults = res.results || [] - const segmentOverrides = results - .concat( + + let segmentOverrides + if (this.props.changeRequest?.change_sets) { + // Add changesets to existing segment overrides + const mergedFeatureStates = mergeChangeSets( + this.props.changeRequest.change_sets, + featureStates, + this.props.changeRequest.conflicts, + ) + + // Get segment IDs marked for deletion + const segmentIdsToDelete = + this.props.changeRequest.change_sets?.flatMap( + (changeSet) => changeSet.segment_ids_to_delete_overrides || [], + ) || [] + + segmentOverrides = results.map((currentSegmentOverride) => { + const changedFeatureState = mergedFeatureStates.find( + (featureState) => + featureState.feature_segment?.segment === + currentSegmentOverride.segment, + ) + + // Any segment_ids_to_delete_overrides should be marked as toRemove + const toRemove = segmentIdsToDelete.includes( + currentSegmentOverride.segment, + ) + + if (changedFeatureState) { + return { + ...currentSegmentOverride, + ...changedFeatureState, + id: changedFeatureState.id || currentSegmentOverride.id, + is_feature_specific: + changedFeatureState.feature_segment?.is_feature_specific, + multivariate_options: + changedFeatureState.multivariate_feature_state_values || [], + priority: changedFeatureState.feature_segment?.priority || 0, + segment: changedFeatureState.feature_segment?.segment, + segment_name: + changedFeatureState.feature_segment?.segment_name || + currentSegmentOverride.segment_name, + toRemove, + uuid: + changedFeatureState.feature_segment?.uuid || + currentSegmentOverride.uuid, + value: Utils.featureStateToValue( + changedFeatureState.feature_state_value, + ), + } + } + + return toRemove + ? { ...currentSegmentOverride, toRemove } + : currentSegmentOverride + }) + + // Add any new segment overrides from the changesets + mergedFeatureStates + .filter( + (featureState) => + !!featureState.feature_segment?.segment && + !results.find( + (currentOverride) => + currentOverride.segment === + featureState.feature_segment.segment, + ), + ) + .forEach((newFeatureState) => { + // Look up segment metadata from segments API to get segment name + const segmentMetadata = segmentsRes.results?.find( + (segment) => + segment.id === newFeatureState.feature_segment?.segment, + ) + + segmentOverrides.push({ + enabled: newFeatureState.enabled, + environment: newFeatureState.environment, + feature: newFeatureState.feature, + id: newFeatureState.id, + is_feature_specific: + newFeatureState.feature_segment?.is_feature_specific, + multivariate_options: + newFeatureState.multivariate_feature_state_values || [], + priority: newFeatureState.feature_segment?.priority || 0, + segment: newFeatureState.feature_segment?.segment, + segment_name: + newFeatureState.feature_segment?.segment_name || + segmentMetadata?.name || + 'Unknown Segment', + uuid: newFeatureState.feature_segment?.uuid, + value: Utils.featureStateToValue( + newFeatureState.feature_state_value, + ), + }) + }) + } else { + segmentOverrides = results.concat( (this.props.newSegmentOverrides || []).map((v, i) => ({ ...v, })), ) - .map((v, i) => ({ - ...v, - originalPriority: i, - priority: i, - })) + } + segmentOverrides = segmentOverrides.map((v, i) => ({ + ...v, + originalPriority: i, + priority: i, + })) + const originalSegmentOverrides = _.cloneDeep(segmentOverrides) this.setState({ environmentVariations: diff --git a/frontend/web/components/diff/DiffSegmentOverrides.tsx b/frontend/web/components/diff/DiffSegmentOverrides.tsx index d73143562e3a..0d57f8d6378e 100644 --- a/frontend/web/components/diff/DiffSegmentOverrides.tsx +++ b/frontend/web/components/diff/DiffSegmentOverrides.tsx @@ -130,7 +130,7 @@ const DiffSegmentOverrides: FC = ({ +
Created
{created.length}
} diff --git a/frontend/web/components/modals/ChangeRequestModal.tsx b/frontend/web/components/modals/ChangeRequestModal.tsx index bd9d55843d68..ce3128aeac83 100644 --- a/frontend/web/components/modals/ChangeRequestModal.tsx +++ b/frontend/web/components/modals/ChangeRequestModal.tsx @@ -21,6 +21,7 @@ import { import { Req } from 'common/types/requests' import getUserDisplayName from 'common/utils/getUserDisplayName' import ChangeRequestConflictCheck from 'components/ChangeRequestConflictCheck' +import { getChangeRequestLiveDate } from 'common/utils/getChangeRequestLiveDate' interface ChangeRequestModalProps { changeRequest?: ChangeRequest @@ -58,7 +59,7 @@ const ChangeRequestModal: FC = ({ ) // const [groups, setGroups] = useState([]) const [liveFrom, setLiveFrom] = useState( - changeRequest?.feature_states[0]?.live_from, + getChangeRequestLiveDate(changeRequest)?.toISOString(), ) const [title, setTitle] = useState(changeRequest?.title ?? '') const [showUsers, setShowUsers] = useState(false) @@ -72,7 +73,8 @@ const ChangeRequestModal: FC = ({ }) useEffect(() => { - const currLiveFromDate = changeRequest?.feature_states[0]?.live_from + const currLiveFromDate = + getChangeRequestLiveDate(changeRequest)?.toISOString() if (!currLiveFromDate) { return setLiveFrom(showAssignees ? currDate.toISOString() : undefined) } diff --git a/frontend/web/components/modals/create-feature/index.js b/frontend/web/components/modals/create-feature/index.js index 310bace32f36..e61619bfb853 100644 --- a/frontend/web/components/modals/create-feature/index.js +++ b/frontend/web/components/modals/create-feature/index.js @@ -616,6 +616,7 @@ const Index = class extends Component { projectFlag.multivariate_options.length && controlValue < 0 const existingChangeRequest = this.props.changeRequest + const isVersionedChangeRequest = existingChangeRequest && isVersioned const hideIdentityOverridesTab = Utils.getShouldHideIdentityOverridesTab() const noPermissions = this.props.noPermissions let regexValid = true @@ -649,6 +650,10 @@ const Index = class extends Component { this.fetchChangeRequests(true) this.fetchScheduledChangeRequests(true) } + + if (this.props.changeRequest) { + this.close() + } }} > {( @@ -751,7 +756,8 @@ const Index = class extends Component { description, featureStateId: this.props.changeRequest && - this.props.changeRequest.feature_states[0].id, + this.props.changeRequest.feature_states?.[0] + ?.id, id: this.props.changeRequest && this.props.changeRequest.id, @@ -918,7 +924,8 @@ const Index = class extends Component { onSaveFeatureValue={saveFeatureValue} />
- {!existingChangeRequest && ( + {(!existingChangeRequest || + isVersionedChangeRequest) && ( { toast('Error updating the Flag', 'danger') return } else { + const isEditingChangeRequest = + this.props.changeRequest && changeRequest const operation = createdFlag || isCreate ? 'Created' : 'Updated' const type = changeRequest ? 'Change Request' : 'Feature' - const toastText = `${operation} ${type}` + const toastText = isEditingChangeRequest + ? `Updated ${type}` + : `${operation} ${type}` const toastAction = changeRequest ? { buttonText: 'Open', diff --git a/frontend/web/components/pages/ChangeRequestDetailPage.tsx b/frontend/web/components/pages/ChangeRequestDetailPage.tsx index 028536c3590a..69510ced5971 100644 --- a/frontend/web/components/pages/ChangeRequestDetailPage.tsx +++ b/frontend/web/components/pages/ChangeRequestDetailPage.tsx @@ -6,6 +6,9 @@ import { useGetMyGroupsQuery } from 'common/services/useMyGroup' import CreateFeatureModal from 'components/modals/create-feature' import AccountStore from 'common/stores/account-store' import AppActions from 'common/dispatcher/app-actions' +import { mergeChangeSets } from 'common/services/useChangeRequest' +import { getFeatureStates } from 'common/services/useFeatureState' +import { getStore } from 'common/store' import { ChangeRequest, Environment, @@ -156,29 +159,67 @@ const ChangeRequestDetailPage: FC = ({ match }) => { }) } - const editChangeRequest = ( + const editChangeRequest = async ( projectFlag: ProjectFlag, environmentFlag: FeatureState, ) => { if (!changeRequest) return + + const environment: Environment = ProjectStore.getEnvironment( + environmentId, + ) as any + + const isVersioned = !!environment?.use_v2_feature_versioning + let changedEnvironmentFlag = environmentFlag + + if (isVersioned && changeRequest.change_sets) { + // Convert the changesets into a feature state + const currentFeatureStatesResponse = await getFeatureStates(getStore(), { + environment: environment.id, + feature: projectFlag.id, + }) + const mergedStates = mergeChangeSets( + changeRequest.change_sets, + currentFeatureStatesResponse.data.results, + changeRequest.conflicts, + ) + const mergedEnvFlag = mergedStates.find( + (v) => !v.feature_segment?.segment, + ) + if (mergedEnvFlag) { + changedEnvironmentFlag = { + ...environmentFlag, + ...mergedEnvFlag, + feature_state_value: Utils.featureStateToValue( + mergedEnvFlag.feature_state_value, + ), + } + } + } else if (!isVersioned && changeRequest.feature_states?.[0]) { + changedEnvironmentFlag = { + ...environmentFlag, + enabled: changeRequest.feature_states[0].enabled, + feature_state_value: Utils.featureStateToValue( + changeRequest.feature_states[0].feature_state_value, + ), + } + } + openModal( 'Edit Change Request', , 'side-modal create-feature-modal', @@ -310,7 +351,7 @@ const ChangeRequestDetailPage: FC = ({ match }) => { scheduledDate={getChangeRequestLiveDate(changeRequest)} deleteChangeRequest={deleteChangeRequest} editChangeRequest={ - !isVersioned && !changeRequest?.committed_at + !changeRequest?.committed_at ? () => editChangeRequest(projectFlag, environmentFlag) : undefined } @@ -494,12 +535,13 @@ export const ChangeRequestPageInner: FC = ({ changeRequest && changeRequest.user && orgUsers.find((v) => v.id === changeRequest.user) - + const isYours = AccountStore.getUserId() === changeRequest.user return (