diff --git a/apps/studio/components/interfaces/Replication/CreatePipelineModal.tsx b/apps/studio/components/interfaces/Replication/CreatePipelineModal.tsx new file mode 100644 index 00000000000..0722c93913e --- /dev/null +++ b/apps/studio/components/interfaces/Replication/CreatePipelineModal.tsx @@ -0,0 +1,144 @@ +import { useParams } from 'common' +import { useCreatePipelineMutation } from 'data/replication/create-pipeline-mutation' +import { toast } from 'sonner' +import { Button, Form, Input, Modal } from 'ui' + +export interface CreatePipelineModalProps { + visible: boolean + onClose: () => void +} + +const CreatePipelineModal = ({ visible, onClose }: CreatePipelineModalProps) => { + const { ref } = useParams() + const { mutate: createPipeline, isLoading: isCreating } = useCreatePipelineMutation({ + onSuccess: (res) => { + toast.success('Successfully created pipeline') + onClose() + }, + }) + + const validate = (values: any) => { + const errors = {} as any + if (!values.source_id) { + errors.source_id = 'Please enter a Source Id for your sink' + } + if (!values.sink_id) { + errors.sink_id = 'Please enter a Sink Id for your sink' + } + if (!values.publication_name) { + errors.publication_name = 'Please enter a Publication Name for your sink' + } + if (!values.max_size) { + errors.max_size = 'Please enter a Max Size for your sink' + } + if (!values.max_fill_secs) { + errors.max_size = 'Please enter a Max Fill Seconds for your sink' + } + return errors + } + + const onSubmit = (values: any) => { + console.log('onSubmit') + createPipeline({ + projectRef: ref!, + source_id: Number(values.source_id), + sink_id: Number(values.sink_id), + publication_name: values.publication_name, + config: { + config: { max_size: Number(values.max_size), max_fill_secs: Number(values.max_fill_secs) }, + }, + }) + } + + const initialValues = { + source_id: '', + sink_id: '', + publication_name: '', + max_size: 10000, + max_fill_secs: 10, + } + return ( + onClose()} + > +
+ {({ values }: { values: any }) => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ) + }} + +
+ ) +} + +export default CreatePipelineModal diff --git a/apps/studio/components/interfaces/Replication/DeletePipepineModal.tsx b/apps/studio/components/interfaces/Replication/DeletePipepineModal.tsx new file mode 100644 index 00000000000..8743409d4f1 --- /dev/null +++ b/apps/studio/components/interfaces/Replication/DeletePipepineModal.tsx @@ -0,0 +1,51 @@ +import { useParams } from 'common' +import { useDeletePipelineMutation } from 'data/replication/delete-pipeline-mutation' +import { toast } from 'sonner' +import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' + +export type DeletePipelineModalProps = { + visible: boolean + title: string + pipelineId: number + onClose: () => void +} + +export const DeletePipelineModal = ({ + visible, + title, + pipelineId, + onClose, +}: DeletePipelineModalProps) => { + const { ref } = useParams() + const { mutate: deletePipeline, isLoading: isCreating } = useDeletePipelineMutation({ + onSuccess: (res) => { + toast.success('Successfully deleted pipeline') + onClose() + }, + }) + const onConfirm = () => { + deletePipeline({ + projectRef: ref!, + pipelineId, + }) + } + return ( + { + onClose() + }} + onConfirm={onConfirm} + > +
+

+ Are you sure you want to delete the selected pipeline? This action cannot be undone. +

+
+
+ ) +} diff --git a/apps/studio/components/interfaces/Replication/Pipelines.tsx b/apps/studio/components/interfaces/Replication/Pipelines.tsx index 73c163f0067..ba74f6b11ad 100644 --- a/apps/studio/components/interfaces/Replication/Pipelines.tsx +++ b/apps/studio/components/interfaces/Replication/Pipelines.tsx @@ -8,6 +8,8 @@ import Table from 'components/to-be-cleaned/Table' import { useState } from 'react' import { toast } from 'sonner' import { Button } from 'ui' +import { DeletePipelineModal } from './DeletePipepineModal' +import CreatePipelineModal from './CreatePipelineModal' export const ReplicationPipelines = () => { const { ref } = useParams() @@ -94,6 +96,16 @@ export const ReplicationPipelines = () => { + setShowCreatePipelineModal(false)} + /> + setShowDeletePipelineModal(false)} + /> ) } diff --git a/apps/studio/data/replication/create-pipeline-mutation.ts b/apps/studio/data/replication/create-pipeline-mutation.ts new file mode 100644 index 00000000000..aa1a93338a4 --- /dev/null +++ b/apps/studio/data/replication/create-pipeline-mutation.ts @@ -0,0 +1,72 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { toast } from 'sonner' + +import type { ResponseError } from 'types' +import { replicationKeys } from './keys' +import { handleError, post } from 'data/fetchers' + +export type CreatePipelineParams = { + projectRef: string + source_id: number + sink_id: number + publication_name: string + config: { config: { max_size: number; max_fill_secs: number } } +} + +async function createPipeline( + { + projectRef, + source_id, + sink_id, + publication_name, + config: { + config: { max_size, max_fill_secs }, + }, + }: CreatePipelineParams, + signal?: AbortSignal +) { + if (!projectRef) throw new Error('projectRef is required') + + const { data, error } = await post('/platform/replication/{ref}/pipelines', { + params: { path: { ref: projectRef } }, + body: { source_id, sink_id, publication_name, config: { config: { max_size, max_fill_secs } } }, + signal, + }) + if (error) { + handleError(error) + } + + return data +} + +type CreatePipelineData = Awaited> + +export const useCreatePipelineMutation = ({ + onSuccess, + onError, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => createPipeline(vars), + { + async onSuccess(data, variables, context) { + const { projectRef } = variables + await queryClient.invalidateQueries(replicationKeys.pipelines(projectRef)) + await onSuccess?.(data, variables, context) + }, + async onError(data, variables, context) { + if (onError === undefined) { + toast.error(`Failed to create pipeline: ${data.message}`) + } else { + onError(data, variables, context) + } + }, + ...options, + } + ) +} diff --git a/apps/studio/data/replication/delete-pipeline-mutation.ts b/apps/studio/data/replication/delete-pipeline-mutation.ts new file mode 100644 index 00000000000..f6253db67a9 --- /dev/null +++ b/apps/studio/data/replication/delete-pipeline-mutation.ts @@ -0,0 +1,60 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { toast } from 'sonner' + +import type { ResponseError } from 'types' +import { replicationKeys } from './keys' +import { handleError, del } from 'data/fetchers' + +export type DeletePipelineParams = { + projectRef: string + pipelineId: number +} + +async function deletePipeline( + { projectRef, pipelineId }: DeletePipelineParams, + signal?: AbortSignal +) { + if (!projectRef) throw new Error('projectRef is required') + + const { data, error } = await del('/platform/replication/{ref}/pipelines/{pipeline_id}', { + params: { path: { ref: projectRef, pipeline_id: pipelineId } }, + signal, + }) + if (error) { + handleError(error) + } + + return data +} + +type DeletePipelineData = Awaited> + +export const useDeletePipelineMutation = ({ + onSuccess, + onError, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => deletePipeline(vars), + { + async onSuccess(data, variables, context) { + const { projectRef } = variables + await queryClient.invalidateQueries(replicationKeys.pipelines(projectRef)) + await onSuccess?.(data, variables, context) + }, + async onError(data, variables, context) { + if (onError === undefined) { + toast.error(`Failed to delete pipeline: ${data.message}`) + } else { + onError(data, variables, context) + } + }, + ...options, + } + ) +}