diff --git a/frontend/common/services/useReleasePipelines.ts b/frontend/common/services/useReleasePipelines.ts index 24c9989c5466..d1c494052582 100644 --- a/frontend/common/services/useReleasePipelines.ts +++ b/frontend/common/services/useReleasePipelines.ts @@ -20,6 +20,19 @@ export const releasePipelinesService = service url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/add-feature/`, }), }), + cloneReleasePipeline: builder.mutation< + Res['releasePipeline'], + Req['cloneReleasePipeline'] + >({ + invalidatesTags: [{ id: 'LIST', type: 'ReleasePipelines' }], + query: (query: Req['cloneReleasePipeline']) => ({ + body: { + name: query.name, + }, + method: 'POST', + url: `projects/${query.projectId}/release-pipelines/${query.pipelineId}/clone/`, + }), + }), createReleasePipeline: builder.mutation< Res['releasePipeline'], Req['createReleasePipeline'] @@ -168,6 +181,7 @@ export async function removeFeatureFromReleasePipeline( export const { useAddFeatureToReleasePipelineMutation, + useCloneReleasePipelineMutation, useCreateReleasePipelineMutation, useDeleteReleasePipelineMutation, useGetReleasePipelineQuery, diff --git a/frontend/common/types/requests.ts b/frontend/common/types/requests.ts index 15bc6f6dbc2c..b4863ee65619 100644 --- a/frontend/common/types/requests.ts +++ b/frontend/common/types/requests.ts @@ -741,5 +741,10 @@ export type Req = { pipelineId: number featureId: number } + cloneReleasePipeline: { + projectId: number + pipelineId: number + name: string + } // END OF TYPES } diff --git a/frontend/web/components/release-pipelines/CloneReleasePipelineModal.tsx b/frontend/web/components/release-pipelines/CloneReleasePipelineModal.tsx new file mode 100644 index 000000000000..1275530613c0 --- /dev/null +++ b/frontend/web/components/release-pipelines/CloneReleasePipelineModal.tsx @@ -0,0 +1,84 @@ +import { useEffect, useState } from 'react' +import { useCloneReleasePipelineMutation } from 'common/services/useReleasePipelines' +import Utils from 'common/utils/utils' +import Button from 'components/base/forms/Button' +import InputGroup from 'components/base/forms/InputGroup' + +type CloneReleasePipelineModalProps = { + projectId: number + pipelineId: number +} + +const CloneReleasePipelineModal = ({ + pipelineId, + projectId, +}: CloneReleasePipelineModalProps) => { + const [name, setName] = useState('') + + const [ + cloneReleasePipeline, + { + error: cloneReleasePipelineError, + isError: isCloningError, + isLoading: isCloning, + isSuccess: isCloningSuccess, + }, + ] = useCloneReleasePipelineMutation() + + useEffect(() => { + if (isCloningSuccess) { + closeModal2() + toast('Release pipeline cloned successfully') + return + } + + if (isCloningError) { + toast( + cloneReleasePipelineError?.data?.detail ?? + 'Error cloning release pipeline', + 'danger', + ) + return + } + }, [isCloningSuccess, isCloningError, cloneReleasePipelineError]) + + return ( +
+ ) => { + setName(Utils.safeParseEventValue(event)) + }} + isValid={name && name.length} + type='text' + name='name' + id='name' + placeholder='E.g. Beta Release' + /> +
+ + +
+
+ ) +} + +export type { CloneReleasePipelineModalProps } +export default CloneReleasePipelineModal diff --git a/frontend/web/components/release-pipelines/ReleasePipelinesList.tsx b/frontend/web/components/release-pipelines/ReleasePipelinesList.tsx index a060876a2d91..67d4c5fb6084 100644 --- a/frontend/web/components/release-pipelines/ReleasePipelinesList.tsx +++ b/frontend/web/components/release-pipelines/ReleasePipelinesList.tsx @@ -8,6 +8,7 @@ import PanelSearch from 'components/PanelSearch' import Tag from 'components/tags/Tag' import { useEffect } from 'react' import ChangeReleasePipelineStatusModal from './ChangeReleasePipelineStatusModal' +import CloneReleasePipelineModal from './CloneReleasePipelineModal' const NoReleasePipelines = ({ projectId }: { projectId: number }) => { const history = useHistory() @@ -87,7 +88,12 @@ const ReleasePipelinesList = ({ 'danger', ) } - }, [isDeletingSuccess, isDeletingError, deleteReleasePipelineError]) + + }, [ + isDeletingSuccess, + isDeletingError, + deleteReleasePipelineError, + ]) const openChangeReleasePipelineStatusModal = ( projectId: number, @@ -208,6 +214,18 @@ const ReleasePipelinesList = ({ ? 'Cannot unpublish a release pipeline with in-flight features' : undefined, }, + { + icon: 'copy' as IconName, + label: 'Clone', + onClick: () => + openModal2( + 'Clone Release Pipeline', + , + ), + }, { disabled: isDeleting || isPublished, icon: 'trash-2',