diff --git a/frontend/web/components/modals/CreateFlag.js b/frontend/web/components/modals/CreateFlag.js index 276b8f7f9de9..cdfdaf11f936 100644 --- a/frontend/web/components/modals/CreateFlag.js +++ b/frontend/web/components/modals/CreateFlag.js @@ -54,6 +54,7 @@ import { IonIcon } from '@ionic/react' import { warning } from 'ionicons/icons' import FeaturePipelineStatus from 'components/release-pipelines/FeaturePipelineStatus' import { FlagValueFooter } from './FlagValueFooter' +import FeatureInPipelineGuard from 'components/release-pipelines/FeatureInPipelineGuard' const CreateFlag = class extends Component { static displayName = 'CreateFlag' @@ -905,6 +906,7 @@ const CreateFlag = class extends Component { multivariate_options={multivariate_options} environmentVariations={environmentVariations} isEdit={isEdit} + projectId={this.props.projectId} error={error?.initial_value?.[0]} canCreateFeature={createFeature} identity={identity} @@ -1130,60 +1132,86 @@ const CreateFlag = class extends Component { 'features', featureLimitAlert.percentage, )} - - Environment Value{' '} - - - } - place='top' + + ( + <> +
Environment Value
+ + This feature is in{' '} + + {matchingReleasePipeline?.name} + {' '} + release pipeline and its value + cannot be changed + + + )} > - {Constants.strings.ENVIRONMENT_OVERRIDE_DESCRIPTION( - _.find(project.environments, { - api_key: this.props.environmentId, - }).name, + + Environment Value{' '} + + + } + place='top' + > + {Constants.strings.ENVIRONMENT_OVERRIDE_DESCRIPTION( + _.find(project.environments, { + api_key: this.props.environmentId, + }).name, + )} + + + {Value( + error, + projectAdmin, + createFeature, )} -
- {Value( - error, - projectAdmin, - createFeature, - )} - {isEdit && ( - <> - - - - )} - + {isEdit && ( + <> + + + + )} + + {!existingChangeRequest && ( @@ -1209,314 +1237,348 @@ const CreateFlag = class extends Component { > {!identity && isEdit && ( -
- -
- - Segment Overrides{' '} - - - } - place='top' + ( + <> +
+ Segment Overrides{' '} +
+ - { - Constants.strings - .SEGMENT_OVERRIDES_DESCRIPTION - } -
-
- - {({ - permission: - manageSegmentOverrides, - }) => - !this.state - .showCreateSegment && - !!manageSegmentOverrides && - !this.props.disableCreate && ( -
- -
- ) - } -
- {!this.state.showCreateSegment && - !noPermissions && ( - - )} -
- {this.props.segmentOverrides ? ( - - {({ - permission: - manageSegmentOverrides, - }) => { - const isReadOnly = - !manageSegmentOverrides - return ( - <> - - - - this.setState({ - showCreateSegment, - }) - } - readOnly={isReadOnly} - is4Eyes={is4Eyes} - showEditSegment - showCreateSegment={ - this.state - .showCreateSegment - } - feature={projectFlag.id} - projectId={ - this.props.projectId - } - multivariateOptions={ - multivariate_options - } - environmentId={ - this.props - .environmentId - } - value={ - this.props - .segmentOverrides - } - controlValue={ - initial_value - } - onChange={(v) => { - this.setState({ - segmentsChanged: true, - }) - this.props.updateSegments( - v, - ) - }} - /> - - ) - }} - - ) : ( -
- -
- )} - {!this.state.showCreateSegment && ( - - )} - {!this.state.showCreateSegment && ( -
-

- {is4Eyes && isVersioned - ? `This will create a change request ${ - isVersioned - ? 'with any value and segment override changes ' - : '' - }for the environment` - : 'This will update the segment overrides for the environment'}{' '} - + This feature is in{' '} + { - _.find( - project.environments, - { - api_key: - this.props - .environmentId, - }, - ).name + matchingReleasePipeline?.name } - -

-
- {' '} + release pipeline and no + segment overrides can be + created + + + )} + > +
+ +
+ + Segment Overrides{' '} + + } + place='top' > - {({ - permission: - savePermission, - }) => ( - +
+ + {({ + permission: + manageSegmentOverrides, + }) => + !this.state + .showCreateSegment && + !!manageSegmentOverrides && + !this.props + .disableCreate && ( +
+ +
+ ) + } +
+ {!this.state + .showCreateSegment && + !noPermissions && ( + , + .segmentOverrides, + ) + } + type='button' + theme='secondary' + size='small' + > + {enabledSegment + ? 'Enable All' + : 'Disable All'} + + )} +
+ {this.props.segmentOverrides ? ( + + {({ + permission: + manageSegmentOverrides, + }) => { + const isReadOnly = + !manageSegmentOverrides + return ( + <> + + + + this.setState({ + showCreateSegment, + }) + } + readOnly={isReadOnly} + is4Eyes={is4Eyes} + showEditSegment + showCreateSegment={ + this.state + .showCreateSegment + } + feature={ + projectFlag.id + } + projectId={ + this.props.projectId + } + multivariateOptions={ + multivariate_options + } + environmentId={ + this.props + .environmentId + } + value={ + this.props + .segmentOverrides + } + controlValue={ + initial_value + } + onChange={(v) => { + this.setState({ + segmentsChanged: true, + }) + this.props.updateSegments( + v, ) + }} + /> + + ) + }} + + ) : ( +
+ +
+ )} + {!this.state + .showCreateSegment && ( + + )} + {!this.state + .showCreateSegment && ( +
+

+ {is4Eyes && isVersioned + ? `This will create a change request ${ + isVersioned + ? 'with any value and segment override changes ' + : '' + }for the environment` + : 'This will update the segment overrides for the environment'}{' '} + + { + _.find( + project.environments, + { + api_key: + this.props + .environmentId, + }, + ).name + } + +

+
+ + {({ + permission: + savePermission, + }) => ( + + {({ + permission: + manageSegmentsOverrides, + }) => { + if ( + isVersioned && + is4Eyes + ) { + return Utils.renderWithPermission( + savePermission, + Utils.getManageFeaturePermissionDescription( + is4Eyes, + identity, + ), + , + ) + } - return Utils.renderWithPermission( - manageSegmentsOverrides, - Constants.environmentPermissions( - 'Manage segment overrides', - ), - <> - {!is4Eyes && - isVersioned && ( - <> - - - )} - - , - ) - }} - - )} - + return Utils.renderWithPermission( + manageSegmentsOverrides, + Constants.environmentPermissions( + 'Manage segment overrides', + ), + <> + {!is4Eyes && + isVersioned && ( + <> + + + )} + + , + ) + }} + + )} + +
-
- )} -
+ )} +
+ )} diff --git a/frontend/web/components/release-pipelines/FeatureInPipelineGuard.tsx b/frontend/web/components/release-pipelines/FeatureInPipelineGuard.tsx new file mode 100644 index 000000000000..57959ea196ad --- /dev/null +++ b/frontend/web/components/release-pipelines/FeatureInPipelineGuard.tsx @@ -0,0 +1,48 @@ +import React, { useMemo } from 'react' +import { useGetReleasePipelinesQuery } from 'common/services/useReleasePipelines' +import { ReleasePipeline } from 'common/types/responses' + +interface FeatureInPipelineGuardProps { + projectId: number + featureId: number + children: React.ReactNode + renderFallback: (matchingReleasePipeline: ReleasePipeline) => React.ReactNode + skip?: boolean +} + +const FeatureInPipelineGuard: React.FC = ({ + children, + featureId, + projectId, + renderFallback, + skip = false, +}) => { + const { data: releasePipelines } = useGetReleasePipelinesQuery( + { + projectId, + }, + { + skip: skip || !projectId || !featureId, + }, + ) + + const matchingReleasePipeline = useMemo(() => { + if (!featureId || !releasePipelines?.results) { + return undefined + } + + return releasePipelines.results.find((pipeline) => + pipeline.features?.includes(featureId), + ) + }, [releasePipelines, featureId]) + + const isFeatureInReleasePipeline = !!matchingReleasePipeline?.id + + if (!isFeatureInReleasePipeline) { + return <>{children} + } + + return <>{renderFallback(matchingReleasePipeline)} +} + +export default FeatureInPipelineGuard