From b74df436818aa781e5f1b57741e0f2b95bdf30bb Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 30 Mar 2022 15:07:08 +0200 Subject: [PATCH] fixed all failing tests --- .../Settings/Logs/Logs.constants.ts | 2 +- .../Settings/Logs/LogsPreviewer.tsx | 6 +- studio/hooks/analytics/useLogsPreview.tsx | 6 +- .../[ref]/logs-explorer/sources/[source].tsx | 85 +----- .../pages/projects/LogsPreviewer.test.js | 2 +- .../tests/pages/projects/logs-preview.test.js | 287 ++++++++++++++++++ 6 files changed, 302 insertions(+), 86 deletions(-) create mode 100644 studio/tests/pages/projects/logs-preview.test.js diff --git a/studio/components/interfaces/Settings/Logs/Logs.constants.ts b/studio/components/interfaces/Settings/Logs/Logs.constants.ts index 0e35c90f232..b346c5e9d80 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.constants.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.constants.ts @@ -225,7 +225,7 @@ limit 100 ` default: - return `no sql!! ` + return "" break } } diff --git a/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx b/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx index be27c3ddad3..990669471fb 100644 --- a/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx +++ b/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx @@ -44,14 +44,16 @@ import LoadingOpacity from 'components/ui/LoadingOpacity' interface Props { projectRef: string queryType: QueryType - override: Override + override?: Override condensedLayout?: boolean + tableName?: LogsTableName } export const LogsPreviewer: React.FC = ({ projectRef, queryType, override, condensedLayout = false, + tableName, }) => { const router = useRouter() const { s, te, ts } = router.query @@ -60,7 +62,7 @@ export const LogsPreviewer: React.FC = ({ const [whereFilters, dispatchWhereFilters] = useReducer(filterReducer, {}) console.log('queryType', queryType) - const table = LOGS_TABLES[queryType] + const table = !tableName ? LOGS_TABLES[queryType] : tableName const [ { error, logData, params, newCount, filters, isLoading, oldestTimestamp }, diff --git a/studio/hooks/analytics/useLogsPreview.tsx b/studio/hooks/analytics/useLogsPreview.tsx index d1d8b621d9d..cfca0195216 100644 --- a/studio/hooks/analytics/useLogsPreview.tsx +++ b/studio/hooks/analytics/useLogsPreview.tsx @@ -67,8 +67,7 @@ function useLogsPreview( useEffect(() => { if (filters !== {}) { - const generatedSql = genDefaultQuery(table, options.whereStatementFactory(filters)) - setParams((prev) => ({ ...prev, sql: cleanQuery(generatedSql), rawSql: generatedSql })) + refresh() } }, [JSON.stringify(filters)]) @@ -111,10 +110,11 @@ function useLogsPreview( const { data: countData } = useSWR(countUrl, get, { refreshInterval: 5000 }) const newCount = countData?.result?.[0]?.count ?? 0 - const refresh = () => { + const refresh = async () => { const generatedSql = genDefaultQuery(table, options.whereStatementFactory(filters)) setParams((prev) => ({ ...prev, sql: cleanQuery(generatedSql), rawSql: generatedSql })) setLatestRefresh(new Date().toISOString()) + setSize(1) } let error: null | string | object = swrError ? swrError.message : null diff --git a/studio/pages/project/[ref]/logs-explorer/sources/[source].tsx b/studio/pages/project/[ref]/logs-explorer/sources/[source].tsx index 57d74e73a1f..6ef025b4251 100644 --- a/studio/pages/project/[ref]/logs-explorer/sources/[source].tsx +++ b/studio/pages/project/[ref]/logs-explorer/sources/[source].tsx @@ -11,85 +11,12 @@ export const LogPage: NextPage = () => { const router = useRouter() const { ref, source } = router.query - useEffect(() => { - if (filters.search_query !== s) { - setFilters((prev) => ({ ...prev, search_query: s as string })) - } - if (te !== params.timestamp_end) { - setTo(te as string) - } - if (ts !== params.timestamp_start) { - setFrom(ts as string) - } - }, [s, te, ts]) - - useEffect(() => { - router.push({ - pathname: router.pathname, - query: { - ...router.query, - q: undefined, - s: filters.search_query || '', - ts: params.timestamp_start, - te: params.timestamp_end, - }, - }) - }, [params.timestamp_end, params.timestamp_start, filters.search_query]) - const onSelectTemplate = (template: LogTemplate) => { - setFilters((prev) => ({ ...prev, search_query: template.searchString })) - } - - useEffect(() => { - // runs when any of the filters change - handleRefresh() - }, [whereFilters]) - - const handleRefresh = () => { - refresh() - router.push({ - pathname: router.pathname, - query: { - ...router.query, - te: undefined, - ts: undefined, - }, - }) - } - - const handleSearch: LogSearchCallback = ({ query, to, from, fromMicro, toMicro }) => { - let toValue - if (to || toMicro) { - toValue = toMicro ? toMicro : dayjs(to).valueOf() * 1000 - - setTo(String(toValue)) - } - if (from || fromMicro) { - fromValue = fromMicro ? fromMicro : dayjs(from).valueOf() * 1000 - setFrom(String(fromValue)) - } - setFilters((prev) => ({ ...prev, search_query: query || '' })) - - router.push({ - pathname: router.pathname, - query: { - ...router.query, - s: query || '', - ts: fromValue, - te: toValue, - }, - }) - } - - useEffect(() => { - // console.log('useEffectFilters', whereFilters) - - router.push({ - pathname: router.pathname, - query: { - ...router.query, - ...whereFilters, - }, - }) + // quick fix to get correct queryType + const queryType = { + edge_logs: 'api', + postgres_logs: 'database', + function_logs: 'functions', + function_edge_logs: 'fn_edge', } return ( diff --git a/studio/tests/pages/projects/LogsPreviewer.test.js b/studio/tests/pages/projects/LogsPreviewer.test.js index 70497220eab..582f623a5c1 100644 --- a/studio/tests/pages/projects/LogsPreviewer.test.js +++ b/studio/tests/pages/projects/LogsPreviewer.test.js @@ -257,7 +257,7 @@ test('bug: load older btn does not error out when previous page is empty', async test('log event chart hide', async () => { render() await screen.findByText('Events') - const toggle = await screen.findByText(/Event chart/) + const toggle = await screen.findByText(/Chart/) userEvent.click(toggle) await expect(screen.findByText('Events')).rejects.toThrow() }) diff --git a/studio/tests/pages/projects/logs-preview.test.js b/studio/tests/pages/projects/logs-preview.test.js new file mode 100644 index 00000000000..50c21941b17 --- /dev/null +++ b/studio/tests/pages/projects/logs-preview.test.js @@ -0,0 +1,287 @@ +// mock the fetch function +jest.mock('lib/common/fetch') +import { get } from 'lib/common/fetch' + +// mock mobx +jest.mock('mobx-react-lite') +import { observer } from 'mobx-react-lite' +observer.mockImplementation((v) => v) + +// mock the router +jest.mock('next/router') +import { useRouter } from 'next/router' +const defaultRouterMock = () => { + const router = jest.fn() + router.query = {} + router.push = jest.fn() + router.pathname = 'logs/path' + return router +} +useRouter.mockReturnValue(defaultRouterMock()) + +// mock monaco editor +jest.mock('@monaco-editor/react') +import Editor, { useMonaco } from '@monaco-editor/react' +Editor = jest.fn() +Editor.mockImplementation((props) => { + return ( + + ) +}) +useMonaco.mockImplementation((v) => v) + +// mock usage flags +jest.mock('components/ui/Flag/Flag') +import Flag from 'components/ui/Flag/Flag' +Flag.mockImplementation(({ children }) => <>{children}) +jest.mock('hooks') +import { useFlag } from 'hooks' +useFlag.mockReturnValue(true) + +import { SWRConfig } from 'swr' +jest.mock('components/interfaces/Settings/Logs/LogsPreviewer') +import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' +LogsPreviewer.mockImplementation((props) => { + const Comp = jest.requireActual('components/interfaces/Settings/Logs/LogsPreviewer').default + // wrap with SWR to reset the cache each time + return ( + new Map(), + shouldRetryOnError: false, + }} + > + + + ) +}) + +import { render, fireEvent, waitFor, screen, act } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { getToggleByText } from '../../helpers' +import { wait } from '@testing-library/user-event/dist/utils' +import { logDataFixture } from '../../fixtures' +import { LogsTableName } from 'components/interfaces/Settings/Logs' +import { find } from 'lodash' +beforeEach(() => { + // reset mocks between tests + get.mockReset() + useRouter.mockReset() + useRouter.mockReturnValue(defaultRouterMock()) +}) +test('can display log data and metadata', async () => { + get.mockResolvedValue({ + result: [ + logDataFixture({ + id: 'some-event-happened-id', + metadata: { + my_key: 'something_value', + }, + }), + ], + }) + render() + fireEvent.click(await screen.findByText(/some-event-happened-id/)) + await screen.findByText(/my_key/) + await screen.findByText(/something_value/) +}) + +test('Refresh page', async () => { + get.mockImplementation((url) => { + if (url.includes('count')) return { result: { count: 0 } } + return { + result: [ + logDataFixture({ + id: 'some-event-id', + metadata: { my_key: 'something_value' }, + }), + ], + } + }) + render() + + const row = await screen.findByText(/some-event-id/) + fireEvent.click(row) + await screen.findByText(/my_key/) +}) + +test('Search will trigger a log refresh', async () => { + get.mockImplementation((url) => { + if (url.includes('something')) { + return { + result: [logDataFixture({ id: 'some-event-id' })], + } + } + return { result: [] } + }) + render() + + userEvent.type(screen.getByPlaceholderText(/Search events/), 'something{enter}') + + await waitFor( + () => { + expect(get).toHaveBeenCalledWith(expect.stringContaining('something')) + + // updates router query params + const router = useRouter() + expect(router.push).toHaveBeenCalledWith( + expect.objectContaining({ + pathname: expect.any(String), + query: expect.objectContaining({ + s: expect.stringContaining('something'), + }), + }) + ) + }, + { timeout: 1500 } + ) + await screen.findByText(/some-event-id/) +}) + +test('poll count for new messages', async () => { + get.mockImplementation((url) => { + if (url.includes('count')) { + return { result: [{ count: 125 }] } + } + return { + result: [logDataFixture({ id: 'some-uuid123' })], + } + }) + render() + await waitFor(() => screen.queryByText(/some-uuid123/) === null) + // should display new logs count + await waitFor(() => screen.getByText(/125/)) + + userEvent.click(screen.getByText(/Refresh/)) + await waitFor(() => screen.queryByText(/125/) === null) + await screen.findByText(/some-uuid123/) +}) + +test('s= query param will populate the search bar', async () => { + const router = defaultRouterMock() + router.query = { ...router.query, s: 'someSearch' } + useRouter.mockReturnValue(router) + render() + // should populate search input with the search param + await screen.findByDisplayValue('someSearch') + expect(get).toHaveBeenCalledWith(expect.stringContaining('someSearch')) +}) + +test('te= query param will populate the timestamp to input', async () => { + // get time 20 mins before + const newDate = new Date() + newDate.setMinutes(new Date().getMinutes() - 20) + const isoString = newDate.toISOString() + const unixMicro = newDate.getTime() * 1000 //microseconds + const router = defaultRouterMock() + router.query = { ...router.query, te: unixMicro } + useRouter.mockReturnValue(router) + render() + + await waitFor(() => { + expect(get).toHaveBeenCalledWith( + expect.stringContaining(`timestamp_end=${encodeURIComponent(unixMicro)}`) + ) + }) + userEvent.click(await screen.findByText('Custom')) + expect(get).toHaveBeenCalledWith(expect.stringContaining('timestamp_end=' + unixMicro)) +}) +test('ts= query param will populate the timestamp from input', async () => { + // get time 20 mins before + const newDate = new Date() + newDate.setMinutes(new Date().getMinutes() - 20) + const isoString = newDate.toISOString() + const unixMicro = newDate.getTime() * 1000 //microseconds + const router = defaultRouterMock() + router.query = { ...router.query, ts: unixMicro } + useRouter.mockReturnValue(router) + render() + + await waitFor(() => { + expect(get).toHaveBeenCalledWith( + expect.stringContaining(`timestamp_start=${encodeURIComponent(unixMicro)}`) + ) + }) + userEvent.click(await screen.findByText('Custom')) + await screen.findByText(new RegExp(newDate.getFullYear())) + await screen.findByText(/Apply/) + expect(get).toHaveBeenCalledWith(expect.stringContaining('timestamp_start=' + unixMicro)) +}) + +test('load older btn will fetch older logs', async () => { + get.mockImplementation((url) => { + if (url.includes('count')) { + return {} + } + return { + result: [logDataFixture({ id: 'first event' })], + } + }) + render() + // should display first log but not second + await waitFor(() => screen.getByText('first event')) + await expect(screen.findByText('second event')).rejects.toThrow() + + get.mockResolvedValueOnce({ + result: [logDataFixture({ id: 'second event' })], + }) + // should display first and second log + userEvent.click(await screen.findByText('Load older')) + await screen.findByText('first event') + await screen.findByText('second event') + expect(get).toHaveBeenCalledWith(expect.stringContaining('timestamp_end=')) +}) + +test('bug: load older btn does not error out when previous page is empty', async () => { + // bugfix for https://sentry.io/organizations/supabase/issues/2903331460/?project=5459134&referrer=slack + get.mockImplementation((url) => { + if (url.includes('count')) { + return {} + } + return { result: [] } + }) + render() + + userEvent.click(await screen.findByText('Load older')) + // NOTE: potential race condition, since we are asserting that something DOES NOT EXIST + // wait for 500s to make sure all ui logic is complete + // need to wrap in act because internal react state is changing during this time. + await act(async () => await wait(100)) + + // clicking load older multiple times should not give error + await waitFor(() => { + expect(screen.queryByText(/Sorry/)).toBeNull() + expect(screen.queryByText(/An error occured/)).toBeNull() + expect(screen.queryByText(/undefined/)).toBeNull() + }) +}) + +test('log event chart hide', async () => { + render() + await screen.findByText('Events') + const toggle = await screen.findByText(/Chart/) + userEvent.click(toggle) + await expect(screen.findByText('Events')).rejects.toThrow() +}) + +test('bug: nav backwards with params change results in ui changing', async () => { + // bugfix for https://sentry.io/organizations/supabase/issues/2903331460/?project=5459134&referrer=slack + get.mockImplementation((url) => { + if (url.includes('count')) { + return {} + } + return { data: [] } + }) + const { container, rerender } = render( + + ) + + await expect(screen.findByDisplayValue('simple-query')).rejects.toThrow() + + const router = defaultRouterMock() + router.query = { ...router.query, s: 'simple-query' } + useRouter.mockReturnValue(router) + rerender() + + await screen.findByDisplayValue('simple-query') +})