create publication wip

This commit is contained in:
Raminder Singh committed 2025-02-06 16:48:49 +05:30
1 parent 8ffcf99169
commit 5ec37eb3de
7 files changed
+327 -25

No files matched your search

@@ -0,0 +1,120 @@
import { useParams } from 'common'
import { useCreatePublicationMutation } from 'data/replication/create-publication-mutation'
import { useReplicationTablesQuery } from 'data/replication/tables-query'
import { toast } from 'sonner'
import { Modal, Form, Input, Checkbox, Button } from 'ui'
export interface CreatePublicationModalProps {
visible: boolean
sourceId: number
onClose: () => void
}
const CreatePublicationModal = ({ visible, onClose, sourceId }: CreatePublicationModalProps) => {
const { ref } = useParams()
const { data: tables } = useReplicationTablesQuery({
projectRef: ref,
sourceId,
})
const { mutate: createPublication, isLoading: isCreating } = useCreatePublicationMutation({
onSuccess: (res) => {
toast.success('Successfully created publication')
onClose()
},
})
const validate = (values: any) => {
const errors = {} as any
if (!values.publication_name) {
errors.publication_name = 'Please provide a name for your publication'
}
return errors
}
const onSubmit = (values: any) => {
let requested_tables = []
if (tables) {
for (const table in tables) {
const key = tables[table]
const schema = values[key.schema]
if (schema) {
let lowercase_key = key.name.toLowerCase()
const name = schema[lowercase_key]
if (name) {
if (name.length !== 0) {
requested_tables.push({ schema: key.schema, name: key.name })
}
}
}
}
createPublication({
projectRef: ref!,
sourceId,
name: values.publication_name,
tables: requested_tables,
})
}
}
const initialValues = {
publication_name: '',
}
return (
<Modal
hideFooter
visible={visible}
header="Create publication"
size="xlarge"
onCancel={() => onClose()}
>
<Form onSubmit={onSubmit} initialValues={initialValues} validate={validate}>
{({ values }: { values: any }) => {
return (
<>
<Modal.Content>
<Input
id="publication_name"
name="publication_name"
type="text"
className="w-full"
layout="vertical"
label="Publication name"
/>
</Modal.Content>
<Modal.Content>
<div className="text-sm text-foreground-light">Publication tables</div>
{tables?.length === 0 ? (
<div className="text-sm py-5">No tables in database</div>
) : (
tables?.map((table) => {
let tableLabel = `${table.schema}.${table.name}`
let tableName = `${table.schema}/${table.name}`
return (
<div key={tableName} className="pt-5">
<Checkbox label={tableLabel}></Checkbox>
</div>
)
})
)}
</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 CreatePublicationModal
@@ -7,34 +7,37 @@ import { useReplicationSourcesQuery } from 'data/replication/sources-query'
import { useParams } from 'common'
import { useReplicationPublicationsQuery } from 'data/replication/publications-query'
import { Button } from 'ui'
import { useState } from 'react'
import CreatePublicationModal from './CreatePublicationModal'
export const ReplicationPublications = () => {
const { ref } = useParams()
const { data } = useReplicationSourcesQuery({
projectRef: ref,
})
const [showCreatePublicationModal, setShowCreatePublicationModal] = useState(false)
const sources = data ?? []
let publication_source_id = null
let source_id = null
for (let i = 0; i < sources.length; i++) {
const source = sources[i]
if (source.config.Postgres!.host.startsWith(`db.${ref}`)) {
if (publication_source_id === null) {
publication_source_id = source.id
if (source_id === null) {
source_id = source.id
} else {
throw new Error(`multiple sources for ref ${ref}`)
}
}
}
if (publication_source_id === null) {
if (source_id === null) {
throw new Error(`no sources for ref ${ref}`)
}
const { data: pub_data } = useReplicationPublicationsQuery({
projectRef: ref,
sourceId: publication_source_id,
sourceId: source_id,
})
const publications = pub_data ?? []
@@ -56,6 +59,7 @@ export const ReplicationPublications = () => {
text: 'Create a publication',
},
}}
onClick={() => setShowCreatePublicationModal(true)}
>
New Publication
</ButtonTooltip>
@@ -64,9 +68,7 @@ export const ReplicationPublications = () => {
<Table
head={[
<Table.th key="name">Name</Table.th>,
<Table.th key="tables">
Num Tables
</Table.th>,
<Table.th key="tables">Num Tables</Table.th>,
<Table.th key="edit">Edit</Table.th>,
<Table.th key="delete">Delete</Table.th>,
]}
@@ -96,6 +98,11 @@ export const ReplicationPublications = () => {
</div>
</ScaffoldSection>
</ScaffoldContainer>
<CreatePublicationModal
visible={showCreatePublicationModal}
sourceId={source_id}
onClose={() => setShowCreatePublicationModal(false)}
/>
</>
)
}
@@ -0,0 +1,66 @@
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 CreatePublicationParams = {
projectRef: string
sourceId: number
name: string
tables: { schema: string; name: string }[]
}
async function createPublication(
{ projectRef, sourceId, name, tables }: CreatePublicationParams,
signal?: AbortSignal
) {
if (!projectRef) throw new Error('projectRef is required')
const { data, error } = await post(
'/platform/replication/{ref}/sources/{source_id}/publications',
{
params: { path: { ref: projectRef, source_id: sourceId } },
body: { name, tables },
signal,
}
)
if (error) {
handleError(error)
}
return data
}
type CreatePublicationData = Awaited<ReturnType<typeof createPublication>>
export const useCreatePublicationMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseMutationOptions<CreatePublicationData, ResponseError, CreatePublicationParams>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<CreatePublicationData, ResponseError, CreatePublicationParams>(
(vars) => createPublication(vars),
{
async onSuccess(data, variables, context) {
const { projectRef, sourceId } = variables
await queryClient.invalidateQueries(replicationKeys.publications(projectRef, sourceId))
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to create publication: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
}
)
}
@@ -1,8 +1,6 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
// import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import type { ResponseError } from 'types'
import { replicationKeys } from './keys'
import { handleError, post } from 'data/fetchers'
@@ -11,16 +9,6 @@ export type CreateSourceParams = {
projectRef: string
}
// export async function createSource({ projectRef }: CreateSourceParams) {
// if (!projectRef) throw new Error('projectRef is required')
// const response = await post(`${API_URL}/storage/${projectRef}/buckets`, {
// id,
// })
// if (response.error) throw response.error
// return response
// }
async function createSource({ projectRef }: CreateSourceParams, signal?: AbortSignal) {
if (!projectRef) throw new Error('projectRef is required')
+2
View File
@@ -2,4 +2,6 @@ export const replicationKeys = {
sources: (projectRef: string | undefined) => ['projects', projectRef, 'sources'] as const,
publications: (projectRef: string | undefined, source_id: number) =>
['projects', projectRef, 'sources', source_id, 'publications'] as const,
tables: (projectRef: string | undefined, source_id: number) =>
['projects', projectRef, 'sources', source_id, 'tables'] as const,
}
@@ -0,0 +1,36 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query'
import { get, handleError } from 'data/fetchers'
import { ResponseError } from 'types'
import { replicationKeys } from './keys'
type ReplicationTablesParams = { projectRef?: string; sourceId: number }
async function fetchReplicationTables(
{ projectRef, sourceId }: ReplicationTablesParams,
signal?: AbortSignal
) {
if (!projectRef) throw new Error('projectRef is required')
const { data, error } = await get('/platform/replication/{ref}/sources/{source_id}/tables', {
params: { path: { ref: projectRef, source_id: sourceId } },
signal,
})
if (error) {
handleError(error)
}
return data
}
export type ReplicationTablesData = Awaited<ReturnType<typeof fetchReplicationTables>>
export const useReplicationTablesQuery = <TData = ReplicationTablesData>(
{ projectRef, sourceId }: ReplicationTablesParams,
{ enabled = true, ...options }: UseQueryOptions<ReplicationTablesData, ResponseError, TData> = {}
) =>
useQuery<ReplicationTablesData, ResponseError, TData>(
replicationKeys.tables(projectRef, sourceId),
({ signal }) => fetchReplicationTables({ projectRef, sourceId }, signal),
{ enabled: enabled && typeof projectRef !== 'undefined', ...options }
)
+88 -5
View File
@@ -3328,6 +3328,12 @@ export interface paths {
'/platform/replication/{ref}/sources/{source_id}/publications': {
/** Gets source publications */
get: operations['ReplicationSourcesController_getPublications']
/** Creates a publication */
post: operations['ReplicationSourcesController_createPublication']
}
'/platform/replication/{ref}/sources/{source_id}/tables': {
/** Gets source tables */
get: operations['ReplicationSourcesController_getTables']
}
'/platform/reset-password': {
parameters: {
@@ -8636,6 +8642,30 @@ export interface components {
publish_update?: boolean
tables?: string[] | null
}
CreatePublicationResponse: Record<string, never>
CreateReplicationPublicationBody: {
/** @description Publication name */
name: string
/** @description Publication tables */
tables: components['schemas']['ReplicationTable'][]
}
CreateRoleBody: {
admins?: string[]
can_bypass_rls?: boolean
can_create_db?: boolean
can_create_role?: boolean
can_login?: boolean
config?: Record<string, never>
connection_limit?: number
inherit_role?: boolean
is_replication_role?: boolean
is_superuser?: boolean
member_of?: string[]
members?: string[]
name: string
password?: string
valid_until?: string
}
CreateSchemaBody: {
name: string
owner: string
@@ -10709,10 +10739,6 @@ export interface components {
saml?: components['schemas']['SamlDescriptor']
updated_at?: string
}
Publication: {
name: string
tables: components['schemas']['Table'][]
}
PublicUrlOptions: {
download?: boolean
downloadName?: string
@@ -10764,6 +10790,10 @@ export interface components {
slot_name: string
username: string
}
ReplicationPublication: {
name: string
tables: components['schemas']['ReplicationTable'][]
}
ReplicationSourcesResponse: {
config: {
Postgres?: components['schemas']['ReplicationPostgresConfig']
@@ -10771,6 +10801,10 @@ export interface components {
id: number
tenant_id: string
}
ReplicationTable: {
name: string
schema: string
}
ReportStatusBody: {
databaseIdentifier: string
reportingToken: string
@@ -21620,7 +21654,7 @@ export interface operations {
responses: {
200: {
content: {
'application/json': components['schemas']['Publication'][]
'application/json': components['schemas']['ReplicationPublication'][]
}
}
/** @description Failed to get source publications */
@@ -21629,6 +21663,55 @@ export interface operations {
}
}
}
/** Creates a publication */
ReplicationSourcesController_createPublication: {
parameters: {
path: {
/** @description Project ref */
ref: string
/** @description Source id */
source_id: number
}
}
requestBody: {
content: {
'application/json': components['schemas']['CreateReplicationPublicationBody']
}
}
responses: {
201: {
content: {
'application/json': components['schemas']['CreatePublicationResponse']
}
}
/** @description Failed to create publication */
500: {
content: never
}
}
}
/** Gets source tables */
ReplicationSourcesController_getTables: {
parameters: {
path: {
/** @description Project ref */
ref: string
/** @description Source id */
source_id: number
}
}
responses: {
200: {
content: {
'application/json': components['schemas']['ReplicationTable'][]
}
}
/** @description Failed to get source tables */
500: {
content: never
}
}
}
/** Reset password for email */
ResetPasswordController_resetPassword: {
parameters: {