MessageDetailsPanel.tsx 7.8 KB

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