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