mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
add ui to create and delete pipelines
This commit is contained in:
1 parent
1bd90502ed
commit
88726fc5e3
5 files changed
+339
No files matched your search
@@ -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 (
|
||||
<Modal
|
||||
hideFooter
|
||||
visible={visible}
|
||||
header="Create pipeline"
|
||||
size="xlarge"
|
||||
onCancel={() => onClose()}
|
||||
>
|
||||
<Form onSubmit={onSubmit} initialValues={initialValues} validate={validate}>
|
||||
{({ values }: { values: any }) => {
|
||||
return (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
id="source_id"
|
||||
name="source_id"
|
||||
type="text"
|
||||
className="w-full"
|
||||
layout="vertical"
|
||||
label="Source Id"
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
id="sink_id"
|
||||
name="sink_id"
|
||||
type="text"
|
||||
className="w-full"
|
||||
layout="vertical"
|
||||
label="Sink Id"
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
id="publication_name"
|
||||
name="publication_name"
|
||||
type="text"
|
||||
className="w-full"
|
||||
layout="vertical"
|
||||
label="Publication Name"
|
||||
></Input>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
id="max_size"
|
||||
name="max_size"
|
||||
type="text"
|
||||
className="w-full"
|
||||
layout="vertical"
|
||||
label="Max Size"
|
||||
></Input>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
id="max_fill_secs"
|
||||
name="max_fill_secs"
|
||||
type="text"
|
||||
className="w-full"
|
||||
layout="vertical"
|
||||
label="Max Fill Secs"
|
||||
></Input>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center space-x-2 justify-end">
|
||||
<Button
|
||||
type="default"
|
||||
htmlType="button"
|
||||
disabled={isCreating}
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit" loading={isCreating} disabled={isCreating}>
|
||||
Create
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreatePipelineModal
|
||||
@@ -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 (
|
||||
<ConfirmationModal
|
||||
variant="destructive"
|
||||
visible={visible}
|
||||
title={title}
|
||||
confirmLabel="Delete"
|
||||
confirmLabelLoading="Deleting"
|
||||
onCancel={() => {
|
||||
onClose()
|
||||
}}
|
||||
onConfirm={onConfirm}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
Are you sure you want to delete the selected pipeline? This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
)
|
||||
}
|
||||
@@ -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 = () => {
|
||||
</div>
|
||||
</ScaffoldSection>
|
||||
</ScaffoldContainer>
|
||||
<CreatePipelineModal
|
||||
visible={showCreatePipelineModal}
|
||||
onClose={() => setShowCreatePipelineModal(false)}
|
||||
/>
|
||||
<DeletePipelineModal
|
||||
visible={showDeletePipelineModal}
|
||||
title={`Delete pipeline with id ${pipelineIdToDelete}?`}
|
||||
pipelineId={pipelineIdToDelete}
|
||||
onClose={() => setShowDeletePipelineModal(false)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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<ReturnType<typeof createPipeline>>
|
||||
|
||||
export const useCreatePipelineMutation = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<CreatePipelineData, ResponseError, CreatePipelineParams>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<CreatePipelineData, ResponseError, CreatePipelineParams>(
|
||||
(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,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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<ReturnType<typeof deletePipeline>>
|
||||
|
||||
export const useDeletePipelineMutation = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<DeletePipelineData, ResponseError, DeletePipelineParams>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<DeletePipelineData, ResponseError, DeletePipelineParams>(
|
||||
(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,
|
||||
}
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user