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