LogTable.test.tsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import { screen, waitFor } from '@testing-library/react'
  2. import userEvent from '@testing-library/user-event'
  3. import dayjs from 'dayjs'
  4. import customParseFormat from 'dayjs/plugin/customParseFormat'
  5. import relativeTime from 'dayjs/plugin/relativeTime'
  6. import timezone from 'dayjs/plugin/timezone'
  7. import utc from 'dayjs/plugin/utc'
  8. import { expect, test, vi } from 'vitest'
  9. import { LogTable } from '@/components/interfaces/Settings/Logs/LogTable'
  10. import { customRender as render } from '@/tests/lib/custom-render'
  11. dayjs.extend(customParseFormat)
  12. dayjs.extend(utc)
  13. dayjs.extend(timezone)
  14. dayjs.extend(relativeTime)
  15. vi.mock('next/router', () => import('next-router-mock'))
  16. vi.mock('react-data-grid', () => ({
  17. default: ({ columns, rows, renderers, role, headerRowHeight }: any) => (
  18. <div role={role ?? 'table'}>
  19. {headerRowHeight !== 0 && (
  20. <div role="row">
  21. {columns.map((col: any, colIdx: number) => (
  22. <div key={colIdx} role="columnheader">
  23. {col.renderHeaderCell ? col.renderHeaderCell({}) : col.name}
  24. </div>
  25. ))}
  26. </div>
  27. )}
  28. {rows.map((row: any, rowIdx: number) => (
  29. <div key={rowIdx} role="row">
  30. {columns.map((col: any, colIdx: number) => (
  31. <div key={colIdx} role="cell">
  32. {col.renderCell?.({ row, rowIdx, isCellSelected: false })}
  33. </div>
  34. ))}
  35. </div>
  36. ))}
  37. {rows.length === 0 && renderers?.noRowsFallback}
  38. </div>
  39. ),
  40. Row: ({ row, ...props }: any) => <div role="row" {...props} />,
  41. }))
  42. const fakeMicroTimestamp = dayjs().unix() * 1000
  43. const LOG_DATA = {
  44. id: 'some-uuid',
  45. timestamp: 1621323232312,
  46. event_message: 'event message',
  47. metadata: {
  48. my_key: 'something_value',
  49. },
  50. }
  51. test('can display log data', async () => {
  52. render(
  53. <>
  54. <LogTable projectRef="default" data={[LOG_DATA]} />
  55. </>
  56. )
  57. await screen.findAllByText(LOG_DATA.timestamp)
  58. })
  59. test('Shows total results', async () => {
  60. render(<LogTable projectRef="default" data={[LOG_DATA]} />)
  61. await screen.getByText(/results \(1\)/i)
  62. })
  63. test('can run if no queryType provided', async () => {
  64. const mockRun = vi.fn()
  65. render(
  66. <LogTable
  67. projectRef="projectRef"
  68. data={[
  69. {
  70. id: 'some-uuid',
  71. timestamp: 1621323232312,
  72. event_message: 'some event happened',
  73. metadata: {
  74. my_key: 'something_value',
  75. },
  76. },
  77. ]}
  78. onRun={mockRun}
  79. />
  80. )
  81. const run = await screen.findByText('Run')
  82. await userEvent.click(run)
  83. // expect(mockRun).toBeCalled()
  84. })
  85. test('can run if no queryType provided', async () => {
  86. const mockRun = vi.fn()
  87. render(
  88. <LogTable
  89. data={[
  90. {
  91. id: 'some-uuid',
  92. timestamp: 1621323232312,
  93. event_message: 'some event happened',
  94. metadata: {
  95. my_key: 'something_value',
  96. },
  97. },
  98. ]}
  99. projectRef="abcd"
  100. onRun={mockRun}
  101. />
  102. )
  103. const run = await screen.findByText('Run')
  104. await userEvent.click(run)
  105. // expect(mockRun).toBeCalled()
  106. })
  107. test('dedupes log lines with exact id', async () => {
  108. // chronological mode requires 4 columns
  109. render(
  110. <LogTable
  111. projectRef="projectRef"
  112. data={[
  113. {
  114. id: 'some-uuid',
  115. timestamp: 1621323232312,
  116. event_message: 'some event happened',
  117. metadata: {},
  118. },
  119. {
  120. id: 'some-uuid',
  121. timestamp: 1621323232312,
  122. event_message: 'some event happened',
  123. metadata: {},
  124. },
  125. ]}
  126. />
  127. )
  128. // should only have one element, this line will fail if there are >1 element
  129. await screen.findByText('timestamp')
  130. })
  131. test('can display standard preview table columns', async () => {
  132. render(
  133. <LogTable
  134. projectRef="ref"
  135. queryType="auth"
  136. data={[{ id: '12345', event_message: 'some event message', timestamp: fakeMicroTimestamp }]}
  137. />
  138. )
  139. await waitFor(() => screen.getByText(/some event message/))
  140. await expect(screen.findByText(/12345/)).rejects.toThrow()
  141. await expect(screen.findByText(fakeMicroTimestamp)).rejects.toThrow()
  142. })
  143. test("closes the selection if the selected row's data changes", async () => {
  144. const { rerender } = render(
  145. <LogTable
  146. projectRef="ref"
  147. queryType="auth"
  148. data={[{ id: '1', event_message: 'some event message', timestamp: fakeMicroTimestamp }]}
  149. />
  150. )
  151. const text = await screen.findByText(/some event message/)
  152. await userEvent.click(text)
  153. rerender(
  154. <LogTable
  155. projectRef="ref"
  156. queryType="auth"
  157. data={[{ id: '2', event_message: 'some other message', timestamp: fakeMicroTimestamp }]}
  158. />
  159. )
  160. await expect(screen.findByText(/some event message/)).rejects.toThrow()
  161. await screen.findByText(/some other message/)
  162. })
  163. enum QueryType {
  164. Functions = 'functions',
  165. Api = 'api',
  166. Auth = 'auth',
  167. }
  168. test.each([
  169. {
  170. queryType: QueryType.Functions,
  171. data: [
  172. {
  173. event_message: 'This is a error log\n',
  174. event_type: 'log',
  175. function_id: '001b0b08-331c-403e-810c-a2004b03a019',
  176. level: 'error',
  177. timestamp: 1659545029083869,
  178. id: '3475cf6f-2929-4296-ab44-ce2c17069937',
  179. },
  180. ],
  181. includes: [/ERROR/],
  182. excludes: ['undefined', 'null'],
  183. },
  184. {
  185. queryType: QueryType.Functions,
  186. data: [
  187. {
  188. event_message: 'This is a uncaughtExceptop\n',
  189. event_type: 'uncaughtException',
  190. function_id: '001b0b08-331c-403e-810c-a2004b03a019',
  191. timestamp: 1659545029083869,
  192. id: '4475cf6f-2929-4296-ab44-ce2c17069937',
  193. level: undefined,
  194. },
  195. ],
  196. includes: [/uncaughtException/],
  197. excludes: [/ERROR/],
  198. },
  199. {
  200. queryType: QueryType.Api,
  201. data: [
  202. {
  203. event_message: 'This is a uncaughtException\n',
  204. path: 'this-is-some-path',
  205. method: 'POST',
  206. status_code: 500,
  207. timestamp: 1659545029083869,
  208. id: '4475cf6f-2929-4296-ab44-ce2c17069937',
  209. },
  210. ],
  211. includes: [/POST/, 'this-is-some-path'],
  212. excludes: [],
  213. },
  214. {
  215. queryType: QueryType.Auth,
  216. data: [
  217. {
  218. event_message: JSON.stringify({ msg: 'some message', path: '/auth-path', level: 'info' }),
  219. msg: 'some message',
  220. path: '/auth-path',
  221. level: 'info',
  222. timestamp: 1659545029083869,
  223. id: '4475cf6f-2929-4296-ab44-ce2c17069937',
  224. },
  225. ],
  226. includes: [/auth\-path/, /some message/, /INFO/],
  227. excludes: [/\{/, /\}/],
  228. },
  229. ])('table col renderer for $queryType', async ({ queryType, data, includes, excludes }) => {
  230. render(<LogTable projectRef="ref" queryType={queryType} data={data} />)
  231. await Promise.all([
  232. ...includes.map((text) => screen.findByText(text)),
  233. ...excludes.map((text) => expect(screen.findByText(text)).rejects.toThrow()),
  234. ])
  235. })
  236. test('error message handling', async () => {
  237. // Render LogTable with error as a string
  238. render(<LogTable projectRef="ref" error={'some error message'} />)
  239. expect(screen.getByText(`some error message`)).toBeTruthy()
  240. // Rerender LogTable with error as null
  241. render(<LogTable projectRef="ref" error={null} />)
  242. // Add any additional assertions if LogTable behaves differently when error is null
  243. })
  244. test('no results message handling', async () => {
  245. render(<LogTable projectRef="ref" data={[]} />)
  246. await screen.findByText(/No results/)
  247. await screen.findByText(/Try another search/)
  248. })
  249. test('custom error message: Resources exceeded during query execution', async () => {
  250. const errorFromLogflare = {
  251. error: {
  252. code: 400,
  253. errors: [
  254. {
  255. domain: 'global',
  256. message:
  257. 'Resources exceeded during query execution: The query could not be executed in the allotted memory. Peak usage: 122% of limit.\nTop memory consumer(s):\n ORDER BY operations: 99%\n other/unattributed: 1%\n',
  258. reason: 'resourcesExceeded',
  259. },
  260. ],
  261. message:
  262. 'Resources exceeded during query execution: The query could not be executed in the allotted memory. Peak usage: 122% of limit.\nTop memory consumer(s):\n ORDER BY operations: 99%\n other/unattributed: 1%\n',
  263. status: 'INVALID_ARGUMENT',
  264. },
  265. }
  266. // logs explorer, custom query
  267. const { rerender } = render(<LogTable projectRef="ref" error={errorFromLogflare} />)
  268. // prompt user to reduce selected tables
  269. await screen.findByText(/This query requires too much memory to be executed/)
  270. await screen.findByText(
  271. /Avoid selecting entire objects and instead select specific keys using dot notation/
  272. )
  273. // previewer, prompt to reduce time range
  274. rerender(<LogTable projectRef="ref" queryType="api" error={errorFromLogflare} />)
  275. await screen.findByText(/This query requires too much memory to be executed/)
  276. await screen.findByText(/Avoid querying across a large datetime range/)
  277. await screen.findByText(/Please contact support if this error persists/)
  278. })