EventTriggerList.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import { useParams } from 'common'
  2. import { includes, sortBy } from 'lodash'
  3. import { Check, Copy, Edit, Edit2, MoreVertical, Trash, X } from 'lucide-react'
  4. import Link from 'next/link'
  5. import { useMemo } from 'react'
  6. import {
  7. Badge,
  8. Button,
  9. DropdownMenu,
  10. DropdownMenuContent,
  11. DropdownMenuItem,
  12. DropdownMenuSeparator,
  13. DropdownMenuTrigger,
  14. TableCell,
  15. TableRow,
  16. } from 'ui'
  17. import type { EventTrigger } from './EventTriggerList.utils'
  18. import { BRIVEN_ROLES } from '@/components/interfaces/Database/Roles/Roles.constants'
  19. import { getDatabaseFunctionsHref } from '@/components/interfaces/Database/Triggers/TriggersList/TriggerList.utils'
  20. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  21. interface EventTriggerListProps {
  22. filterString: string
  23. eventTriggers: EventTrigger[]
  24. ownerFilter: string[]
  25. canEdit: boolean
  26. onEditTrigger: (trigger: EventTrigger) => void
  27. onEditTriggerWithAssistant: (trigger: EventTrigger) => void
  28. onDuplicateTrigger: (trigger: EventTrigger) => void
  29. onDeleteTrigger: (trigger: EventTrigger) => void
  30. }
  31. const SYSTEM_OWNERS = new Set<string>(BRIVEN_ROLES)
  32. export const EventTriggerList = ({
  33. filterString,
  34. eventTriggers,
  35. ownerFilter,
  36. canEdit,
  37. onEditTrigger,
  38. onEditTriggerWithAssistant,
  39. onDuplicateTrigger,
  40. onDeleteTrigger,
  41. }: EventTriggerListProps) => {
  42. const { ref: projectRef } = useParams()
  43. const orderedTriggers = useMemo(() => {
  44. const searchValue = filterString.toLowerCase()
  45. const ownerFilterSet = new Set(ownerFilter)
  46. const filteredEventTriggers = eventTriggers.filter((trigger) => {
  47. const matchesOwner =
  48. ownerFilterSet.size === 0 ? true : trigger.owner ? ownerFilterSet.has(trigger.owner) : false
  49. return (
  50. matchesOwner &&
  51. (includes(trigger.name.toLowerCase(), searchValue) ||
  52. includes(trigger.event.toLowerCase(), searchValue) ||
  53. (trigger.function_name && includes(trigger.function_name.toLowerCase(), searchValue)))
  54. )
  55. })
  56. return sortBy(filteredEventTriggers, (trigger) => trigger.name.toLocaleLowerCase())
  57. }, [eventTriggers, ownerFilter, filterString])
  58. if (orderedTriggers.length === 0 && filterString.length === 0 && ownerFilter.length === 0) {
  59. return (
  60. <TableRow>
  61. <TableCell colSpan={6}>
  62. <p className="text-sm text-foreground">No event triggers created yet</p>
  63. <p className="text-sm text-foreground-light">
  64. There are no event triggers configured for this database
  65. </p>
  66. </TableCell>
  67. </TableRow>
  68. )
  69. }
  70. if (orderedTriggers.length === 0 && (filterString.length > 0 || ownerFilter.length > 0)) {
  71. return (
  72. <TableRow>
  73. <TableCell colSpan={6}>
  74. <p className="text-sm text-foreground">No results found</p>
  75. <p className="text-sm text-foreground-light">
  76. {filterString.length > 0
  77. ? `Your search for "${filterString}" did not return any results`
  78. : 'No event triggers match the current filters'}
  79. </p>
  80. </TableCell>
  81. </TableRow>
  82. )
  83. }
  84. return (
  85. <>
  86. {orderedTriggers.map((trigger) => {
  87. const isSystemTrigger = trigger.owner ? SYSTEM_OWNERS.has(trigger.owner) : false
  88. const canEditTrigger = !isSystemTrigger && canEdit
  89. const disabledReason = !canEdit
  90. ? 'You need additional permissions to update event triggers'
  91. : 'System event triggers cannot be edited'
  92. return (
  93. <TableRow key={trigger.oid}>
  94. <TableCell className="space-x-2">
  95. {canEditTrigger ? (
  96. <Button
  97. type="text"
  98. onClick={() => onEditTrigger(trigger)}
  99. title={trigger.name}
  100. className="text-link-table-cell text-left text-sm disabled:opacity-90 disabled:no-underline min-w-0 p-0 hover:bg-transparent font-medium max-w-48 title"
  101. >
  102. {trigger.name}
  103. </Button>
  104. ) : (
  105. <p title={trigger.name} className="truncate text-foreground text-sm font-medium">
  106. {trigger.name}
  107. </p>
  108. )}
  109. </TableCell>
  110. <TableCell>
  111. <Badge>{trigger.event}</Badge>
  112. </TableCell>
  113. <TableCell className="space-x-2">
  114. {trigger.function_name ? (
  115. <Link
  116. href={getDatabaseFunctionsHref(
  117. projectRef,
  118. trigger.function_schema,
  119. trigger.function_name
  120. )}
  121. className="text-link-table-cell block max-w-40 text-foreground-light"
  122. >
  123. {trigger.function_name}
  124. </Link>
  125. ) : (
  126. <p className="truncate text-foreground-light">-</p>
  127. )}
  128. </TableCell>
  129. <TableCell>
  130. {trigger.tags && trigger.tags.length > 0 ? (
  131. <div className="flex gap-2 flex-wrap">
  132. {trigger.tags.map((tag) => (
  133. <Badge key={tag}>{tag}</Badge>
  134. ))}
  135. </div>
  136. ) : (
  137. <p className="truncate text-foreground-light">-</p>
  138. )}
  139. </TableCell>
  140. <TableCell>
  141. <div className="flex items-center justify-center">
  142. {trigger.enabled_mode !== 'DISABLED' ? (
  143. <Check strokeWidth={2} className="text-brand" />
  144. ) : (
  145. <X strokeWidth={2} />
  146. )}
  147. </div>
  148. </TableCell>
  149. <TableCell className="text-right">
  150. {canEditTrigger ? (
  151. <DropdownMenu>
  152. <DropdownMenuTrigger asChild>
  153. <Button
  154. aria-label="More options"
  155. type="default"
  156. className="px-1"
  157. icon={<MoreVertical />}
  158. />
  159. </DropdownMenuTrigger>
  160. <DropdownMenuContent side="bottom" align="end" className="w-52">
  161. <DropdownMenuItem className="space-x-2" onClick={() => onEditTrigger(trigger)}>
  162. <Edit2 size={14} />
  163. <p>Edit trigger</p>
  164. </DropdownMenuItem>
  165. <DropdownMenuItem
  166. className="space-x-2"
  167. onClick={() => onEditTriggerWithAssistant(trigger)}
  168. >
  169. <Edit size={14} />
  170. <p>Edit with Assistant</p>
  171. </DropdownMenuItem>
  172. <DropdownMenuItem
  173. className="space-x-2"
  174. onClick={() => onDuplicateTrigger(trigger)}
  175. >
  176. <Copy size={14} />
  177. <p>Duplicate trigger</p>
  178. </DropdownMenuItem>
  179. <DropdownMenuSeparator />
  180. <DropdownMenuItem
  181. className="space-x-2"
  182. onClick={() => onDeleteTrigger(trigger)}
  183. >
  184. <Trash stroke="red" size={14} />
  185. <p>Delete trigger</p>
  186. </DropdownMenuItem>
  187. </DropdownMenuContent>
  188. </DropdownMenu>
  189. ) : (
  190. <ButtonTooltip
  191. disabled
  192. type="default"
  193. className="px-1"
  194. icon={<MoreVertical />}
  195. tooltip={{
  196. content: {
  197. side: 'bottom',
  198. text: disabledReason,
  199. },
  200. }}
  201. />
  202. )}
  203. </TableCell>
  204. </TableRow>
  205. )
  206. })}
  207. </>
  208. )
  209. }