MessageDetailsPanel.tsx227 lines · main
1import { useEscapeKeydown } from '@radix-ui/react-use-escape-keydown'
2import { useParams } from 'common'
3import dayjs from 'dayjs'
4import { isNil, noop } from 'lodash'
5import { Archive, Clock12, Trash2, X } from 'lucide-react'
6import { useState } from 'react'
7import {
8 Button,
9 ResizablePanel,
10 Separator,
11 Tabs_Shadcn_,
12 TabsContent_Shadcn_,
13 TabsList_Shadcn_,
14 TabsTrigger_Shadcn_,
15} from 'ui'
16
17import { MonacoEditor } from '@/components/grid/components/common/MonacoEditor'
18import { RowAction, RowData } from '@/components/interfaces/Auth/Users/UserOverview'
19import { useDatabaseQueueMessageArchiveMutation } from '@/data/database-queues/database-queue-messages-archive-mutation'
20import { useDatabaseQueueMessageDeleteMutation } from '@/data/database-queues/database-queue-messages-delete-mutation'
21import { PostgresQueueMessage } from '@/data/database-queues/database-queue-messages-infinite-query'
22import { useDatabaseQueueMessageReadMutation } from '@/data/database-queues/database-queue-messages-read-mutation'
23import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
24import { prettifyJSON } from '@/lib/helpers'
25
26export const DATE_FORMAT = 'DD MMM, YYYY HH:mm'
27
28interface MessageDetailsPanelProps {
29 selectedMessage: PostgresQueueMessage
30 setSelectedMessage: (value: number | null) => void
31}
32
33const tryFormatInitialValue = (value: string) => {
34 try {
35 const jsonValue = JSON.parse(value)
36 return JSON.stringify(jsonValue)
37 } catch (err) {
38 if (typeof value === 'string') {
39 return value.replaceAll(`\"`, `"`)
40 } else {
41 return JSON.stringify(value)
42 }
43 }
44}
45
46export const MessageDetailsPanel = ({
47 selectedMessage,
48 setSelectedMessage,
49}: MessageDetailsPanelProps) => {
50 const { id: _id, childId: queueName } = useParams()
51 const { data: project } = useSelectedProjectQuery()
52
53 useEscapeKeydown(() => setSelectedMessage(null))
54
55 const {
56 mutate: archiveMessage,
57 isPending: isLoadingArchive,
58 isSuccess: isSuccessArchive,
59 } = useDatabaseQueueMessageArchiveMutation()
60
61 const {
62 mutate: readMessage,
63 isPending: isLoadingRead,
64 isSuccess: isSuccessRead,
65 } = useDatabaseQueueMessageReadMutation()
66
67 const {
68 mutate: deleteMessage,
69 isPending: isLoadingDelete,
70 isSuccess: isSuccessDelete,
71 } = useDatabaseQueueMessageDeleteMutation()
72
73 const initialValue = JSON.stringify(selectedMessage?.message)
74 const jsonString = prettifyJSON(!isNil(initialValue) ? tryFormatInitialValue(initialValue) : '')
75
76 const [view, setView] = useState<'details' | 'suggestion'>('details')
77
78 return (
79 <ResizablePanel
80 defaultSize="30"
81 maxSize="45"
82 minSize="30"
83 collapsible
84 onResize={(panelSize) => {
85 if (panelSize.asPercentage === 0) {
86 setSelectedMessage(null)
87 }
88 }}
89 className="bg-studio border-t pointer-events-auto"
90 >
91 <Button
92 type="text"
93 className="absolute top-3 right-3 px-1"
94 icon={<X />}
95 onClick={() => setSelectedMessage(null)}
96 />
97
98 <Tabs_Shadcn_
99 value={view}
100 className="flex flex-col h-full"
101 onValueChange={(value: any) => {
102 setView(value)
103 }}
104 >
105 <TabsList_Shadcn_ className="px-5 flex gap-x-4 min-h-[46px]">
106 <TabsTrigger_Shadcn_
107 value="details"
108 className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent shadow-none!"
109 >
110 Overview
111 </TabsTrigger_Shadcn_>
112 </TabsList_Shadcn_>
113 <TabsContent_Shadcn_ value="details" className="w-full mt-0 overflow-y-auto grow">
114 <div className="flex flex-col px-4 py-4 text-sm">
115 <RowData property="Message ID" value={`${selectedMessage.msg_id}`} />
116 <RowData
117 property="Added at"
118 value={dayjs(selectedMessage.enqueued_at).format(DATE_FORMAT)}
119 />
120 <RowData
121 property="Available at"
122 value={dayjs(selectedMessage.vt).format(DATE_FORMAT)}
123 />
124 <RowData property="Retries" value={`${selectedMessage.read_ct}`} />
125
126 <div>
127 <h3 className="text-foreground-light py-1">Payload</h3>
128 <MonacoEditor
129 key={selectedMessage.msg_id}
130 onChange={noop}
131 width="100%"
132 value={jsonString || 'NULL'}
133 language="json"
134 readOnly
135 />
136 </div>
137 </div>
138 <Separator />
139 <div className="flex flex-col px-4 py-4 -space-y-1">
140 {!selectedMessage.archived_at ? (
141 <>
142 <RowAction
143 title="Postpone message"
144 description="The message will be postponed and won't show up in reads for 60 seconds."
145 button={{
146 icon: <Clock12 />,
147 text: 'Postpone',
148 isLoading: isLoadingRead,
149 onClick: () => {
150 readMessage({
151 projectRef: project!.ref,
152 connectionString: project?.connectionString,
153 queueName: queueName!,
154 messageId: selectedMessage.msg_id,
155 duration: 60,
156 })
157 },
158 }}
159 success={
160 isSuccessRead
161 ? {
162 title: 'Postponed',
163 description: 'The message was postponed for 60 seconds.',
164 }
165 : undefined
166 }
167 />
168 <RowAction
169 title="Archive message"
170 description="The message will be marked as archived and hidden from future reads by consumers. You can still access the message later."
171 button={{
172 icon: <Archive />,
173 text: 'Archive',
174 isLoading: isLoadingArchive,
175 type: 'warning',
176 onClick: () => {
177 archiveMessage({
178 projectRef: project!.ref,
179 connectionString: project?.connectionString,
180 queueName: queueName!,
181 messageId: selectedMessage.msg_id,
182 })
183 },
184 }}
185 success={
186 isSuccessArchive
187 ? {
188 title: 'Archived',
189 description: 'The message was archived successfully.',
190 }
191 : undefined
192 }
193 />
194 <RowAction
195 title="Delete message"
196 description="The message cannot be recovered afterwards."
197 button={{
198 icon: <Trash2 />,
199 text: 'Delete',
200 type: 'danger',
201 isLoading: isLoadingDelete,
202 onClick: () => {
203 deleteMessage({
204 projectRef: project!.ref,
205 connectionString: project?.connectionString,
206 queueName: queueName!,
207 messageId: selectedMessage.msg_id,
208 })
209 },
210 }}
211 success={
212 isSuccessDelete
213 ? {
214 title: 'Deleted',
215 description: 'The message was deleted successfully.',
216 }
217 : undefined
218 }
219 />
220 </>
221 ) : null}
222 </div>
223 </TabsContent_Shadcn_>
224 </Tabs_Shadcn_>
225 </ResizablePanel>
226 )
227}