HooksList.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { PermissionAction } from '@supabase/shared-types/out/constants'
  2. import { includes, map as lodashMap, uniqBy } from 'lodash'
  3. import { Search } from 'lucide-react'
  4. import { parseAsBoolean, useQueryState } from 'nuqs'
  5. import { useState } from 'react'
  6. import { InputGroup, InputGroupAddon, InputGroupInput } from 'ui'
  7. import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
  8. import { HooksListEmpty } from './HooksListEmpty'
  9. import { SchemaTable } from './SchemaTable'
  10. import AlertError from '@/components/ui/AlertError'
  11. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  12. import { DocsButton } from '@/components/ui/DocsButton'
  13. import { NoSearchResults } from '@/components/ui/NoSearchResults'
  14. import { useDatabaseHooksQuery } from '@/data/database-triggers/database-triggers-query'
  15. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  16. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  17. import { DOCS_URL } from '@/lib/constants'
  18. export const HooksList = () => {
  19. const { data: project } = useSelectedProjectQuery()
  20. const [, setShowCreateHookForm] = useQueryState('new', parseAsBoolean.withDefault(false))
  21. const {
  22. data: hooks = [],
  23. isPending: isLoading,
  24. isSuccess,
  25. isError,
  26. error,
  27. } = useDatabaseHooksQuery({
  28. projectRef: project?.ref,
  29. connectionString: project?.connectionString,
  30. })
  31. const [filterString, setFilterString] = useState<string>('')
  32. const filteredHooks = hooks.filter((x) =>
  33. includes(x.name.toLowerCase(), filterString.toLowerCase())
  34. )
  35. const filteredHookSchemas = lodashMap(uniqBy(filteredHooks, 'schema'), 'schema')
  36. const { can: canCreateWebhooks, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
  37. PermissionAction.TENANT_SQL_ADMIN_WRITE,
  38. 'triggers'
  39. )
  40. return (
  41. <div className="w-full space-y-4">
  42. <div className="flex items-center justify-between">
  43. <InputGroup className="w-52">
  44. <InputGroupInput
  45. size="tiny"
  46. placeholder="Search for a webhook"
  47. value={filterString}
  48. onChange={(e) => setFilterString(e.target.value)}
  49. />
  50. <InputGroupAddon>
  51. <Search />
  52. </InputGroupAddon>
  53. </InputGroup>
  54. <div className="flex items-center gap-x-2">
  55. <DocsButton href={`${DOCS_URL}/guides/database/webhooks`} />
  56. <ButtonTooltip
  57. onClick={() => setShowCreateHookForm(true)}
  58. disabled={!isPermissionsLoaded || !canCreateWebhooks}
  59. tooltip={{
  60. content: {
  61. side: 'bottom',
  62. text:
  63. isPermissionsLoaded && !canCreateWebhooks
  64. ? 'You need additional permissions to create webhooks'
  65. : undefined,
  66. },
  67. }}
  68. >
  69. Create a new hook
  70. </ButtonTooltip>
  71. </div>
  72. </div>
  73. {isLoading && (
  74. <div className="py-4">
  75. <GenericSkeletonLoader />
  76. </div>
  77. )}
  78. {isError && <AlertError error={error} subject="Failed to retrieve database webhooks" />}
  79. {isSuccess &&
  80. (hooks.length <= 0 ? (
  81. <HooksListEmpty />
  82. ) : (
  83. <>
  84. {filteredHooks.length <= 0 && (
  85. <NoSearchResults
  86. searchString={filterString}
  87. onResetFilter={() => setFilterString('')}
  88. />
  89. )}
  90. {filteredHookSchemas.map((schema) => (
  91. <SchemaTable key={schema} filterString={filterString} schema={schema} />
  92. ))}
  93. </>
  94. ))}
  95. </div>
  96. )
  97. }