mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 14:49:10 +03:00
create publication wip
This commit is contained in:
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,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 }
|
||||
)
|
||||
Vendored
+88
-5
@@ -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: {
|
||||
|
||||
Reference in new issue
Block a user