| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- import { ident, literal, safeSql, type SafeSqlFragment } from '@supabase/pg-meta/src/pg-format'
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { noop } from 'lodash'
- import { Loader } from 'lucide-react'
- import { useState } from 'react'
- import { toast } from 'sonner'
- import { Button, ExpandingTextArea } from 'ui'
- import { executeSql } from '@/data/sql/execute-sql-query'
- import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
- import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- import { timeout } from '@/lib/helpers'
- // Removes some auto-generated Postgrest text
- // Ideally PostgREST wouldn't add this if there is already a comment
- const temp_removePostgrestText = (content: string) => {
- const postgrestTextPk = `Note:\nThis is a Primary Key.<pk/>`
- const postgrestTextFk = `Note:\nThis is a Foreign Key to`
- const pkTextPos = content.lastIndexOf(postgrestTextPk)
- const fkTextPos = content.lastIndexOf(postgrestTextFk)
- let cleansed = content
- if (pkTextPos >= 0) cleansed = cleansed.substring(0, pkTextPos)
- if (fkTextPos >= 0) cleansed = cleansed.substring(0, fkTextPos)
- return cleansed
- }
- interface DescrptionProps {
- content: string
- metadata: { table?: string; column?: string; rpc?: string }
- onChange: (value: string) => void
- }
- const Description = ({ content, metadata, onChange = noop }: DescrptionProps) => {
- const contentText = temp_removePostgrestText(content || '').trim()
- const [value, setValue] = useState(contentText)
- const [isUpdating, setIsUpdating] = useState(false)
- const { data: project } = useSelectedProjectQuery()
- const { table, column, rpc } = metadata
- const hasChanged = value != contentText
- const animateCss = `transition duration-150`
- const { can: canUpdateDescription } = useAsyncCheckPermissions(
- PermissionAction.TENANT_SQL_QUERY,
- '*'
- )
- const updateDescription = async () => {
- if (isUpdating || !canUpdateDescription) return false
- setIsUpdating(true)
- let query: SafeSqlFragment | undefined
- if (table && column)
- query = safeSql`comment on column ${ident('public')}.${ident(table)}.${ident(column)} is ${literal(value)};`
- if (table && !column)
- query = safeSql`comment on table ${ident('public')}.${ident(table)} is ${literal(value)};`
- if (rpc) query = safeSql`comment on function ${ident(rpc)} is ${literal(value)};`
- if (query) {
- try {
- await executeSql({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- sql: query,
- })
- // [Joshen] Temp fix, immediately refreshing the docs fetches stale state
- await timeout(500)
- toast.success(`Successfully updated description`)
- } catch (error: any) {
- toast.error(`Failed to update description: ${error.message}`)
- }
- }
- onChange(value)
- setIsUpdating(false)
- }
- if (!canUpdateDescription) {
- return (
- <span className={`block text-sm ${value ? 'text-foreground' : ''}`}>
- {value || 'No description'}
- </span>
- )
- }
- return (
- <div className="space-y-2 px-0.5">
- <ExpandingTextArea
- className="w-full min-h-auto"
- placeholder="Click to edit."
- value={value}
- onChange={(e: any) => setValue(e.target.value)}
- />
- <div
- className={`flex items-center gap-2 ${
- hasChanged ? 'opacity-100' : 'h-0 cursor-default opacity-0'
- } ${animateCss}`}
- >
- <Button
- type="default"
- disabled={!hasChanged}
- onClick={() => {
- setValue(contentText)
- setIsUpdating(false)
- }}
- >
- Cancel
- </Button>
- <Button disabled={!hasChanged} onClick={updateDescription}>
- {isUpdating ? (
- <Loader className="mx-auto animate-spin" size={14} strokeWidth={2} />
- ) : (
- <span>Save</span>
- )}
- </Button>
- </div>
- </div>
- )
- }
- export default Description
|